@indigoai-us/hq-cli 5.1.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/dist/__tests__/credentials.test.d.ts +5 -0
- package/dist/__tests__/credentials.test.d.ts.map +1 -0
- package/dist/__tests__/credentials.test.js +169 -0
- package/dist/__tests__/credentials.test.js.map +1 -0
- package/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +60 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/auth.d.ts +17 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +269 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/cloud-setup.d.ts +19 -0
- package/dist/commands/cloud-setup.d.ts.map +1 -0
- package/dist/commands/cloud-setup.js +206 -0
- package/dist/commands/cloud-setup.js.map +1 -0
- package/dist/commands/cloud.d.ts +16 -0
- package/dist/commands/cloud.d.ts.map +1 -0
- package/dist/commands/cloud.js +263 -0
- package/dist/commands/cloud.js.map +1 -0
- package/dist/commands/initial-upload.d.ts +67 -0
- package/dist/commands/initial-upload.d.ts.map +1 -0
- package/dist/commands/initial-upload.js +205 -0
- package/dist/commands/initial-upload.js.map +1 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +55 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/sync.d.ts +6 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +104 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +60 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/strategies/link.d.ts +7 -0
- package/dist/strategies/link.d.ts.map +1 -0
- package/dist/strategies/link.js +51 -0
- package/dist/strategies/link.js.map +1 -0
- package/dist/strategies/merge.d.ts +7 -0
- package/dist/strategies/merge.d.ts.map +1 -0
- package/dist/strategies/merge.js +110 -0
- package/dist/strategies/merge.js.map +1 -0
- package/dist/sync-worker.d.ts +11 -0
- package/dist/sync-worker.d.ts.map +1 -0
- package/dist/sync-worker.js +77 -0
- package/dist/sync-worker.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/api-client.d.ts +26 -0
- package/dist/utils/api-client.d.ts.map +1 -0
- package/dist/utils/api-client.js +87 -0
- package/dist/utils/api-client.js.map +1 -0
- package/dist/utils/credentials.d.ts +44 -0
- package/dist/utils/credentials.d.ts.map +1 -0
- package/dist/utils/credentials.js +101 -0
- package/dist/utils/credentials.js.map +1 -0
- package/dist/utils/git.d.ts +13 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +70 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/manifest.d.ts +16 -0
- package/dist/utils/manifest.d.ts.map +1 -0
- package/dist/utils/manifest.js +95 -0
- package/dist/utils/manifest.js.map +1 -0
- package/dist/utils/sync.d.ts +125 -0
- package/dist/utils/sync.d.ts.map +1 -0
- package/dist/utils/sync.js +291 -0
- package/dist/utils/sync.js.map +1 -0
- package/package.json +36 -0
- package/src/__tests__/cloud-setup.test.ts +117 -0
- package/src/__tests__/credentials.test.ts +203 -0
- package/src/__tests__/initial-upload.test.ts +414 -0
- package/src/__tests__/sync.test.ts +627 -0
- package/src/commands/add.ts +74 -0
- package/src/commands/auth.ts +303 -0
- package/src/commands/cloud-setup.ts +251 -0
- package/src/commands/cloud.ts +300 -0
- package/src/commands/initial-upload.ts +263 -0
- package/src/commands/list.ts +66 -0
- package/src/commands/sync.ts +149 -0
- package/src/commands/update.ts +71 -0
- package/src/hq-cloud.d.ts +19 -0
- package/src/index.ts +46 -0
- package/src/strategies/link.ts +62 -0
- package/src/strategies/merge.ts +142 -0
- package/src/sync-worker.ts +82 -0
- package/src/types.ts +47 -0
- package/src/utils/api-client.ts +111 -0
- package/src/utils/credentials.ts +124 -0
- package/src/utils/git.ts +74 -0
- package/src/utils/manifest.ts +111 -0
- package/src/utils/sync.ts +381 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge Sync Strategy (US-007)
|
|
3
|
+
* Copies files from module into HQ, tracks state for conflict detection
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as crypto from 'crypto';
|
|
8
|
+
import { readState, writeState } from '../utils/manifest.js';
|
|
9
|
+
function hashFile(filePath) {
|
|
10
|
+
const content = fs.readFileSync(filePath);
|
|
11
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
12
|
+
}
|
|
13
|
+
function copyRecursive(srcDir, destDir, state, moduleName, hqRoot, filesChanged) {
|
|
14
|
+
if (!fs.existsSync(destDir)) {
|
|
15
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
20
|
+
const destPath = path.join(destDir, entry.name);
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
copyRecursive(srcPath, destPath, state, moduleName, hqRoot, filesChanged);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const relativeDest = path.relative(hqRoot, destPath).replace(/\\/g, '/');
|
|
26
|
+
const newHash = hashFile(srcPath);
|
|
27
|
+
// Check if file exists and has been modified by user
|
|
28
|
+
if (fs.existsSync(destPath)) {
|
|
29
|
+
const existingHash = hashFile(destPath);
|
|
30
|
+
const lastSyncedHash = state.files[relativeDest]?.hash;
|
|
31
|
+
if (lastSyncedHash && existingHash !== lastSyncedHash && existingHash !== newHash) {
|
|
32
|
+
// User modified the file since last sync - skip (conflict)
|
|
33
|
+
console.warn(` Conflict: ${relativeDest} has local changes, skipping`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (existingHash === newHash) {
|
|
37
|
+
// File unchanged, skip
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Copy file
|
|
42
|
+
fs.copyFileSync(srcPath, destPath);
|
|
43
|
+
filesChanged.count++;
|
|
44
|
+
// Track in state
|
|
45
|
+
state.files[relativeDest] = {
|
|
46
|
+
hash: newHash,
|
|
47
|
+
syncedAt: new Date().toISOString(),
|
|
48
|
+
fromModule: moduleName,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function mergeSync(module, moduleDir, hqRoot) {
|
|
54
|
+
let state = readState(hqRoot);
|
|
55
|
+
if (!state) {
|
|
56
|
+
state = { version: '1', files: {} };
|
|
57
|
+
}
|
|
58
|
+
const filesChanged = { count: 0 };
|
|
59
|
+
for (const mapping of module.paths) {
|
|
60
|
+
const srcPath = path.join(moduleDir, mapping.src);
|
|
61
|
+
const destPath = path.join(hqRoot, mapping.dest);
|
|
62
|
+
if (!fs.existsSync(srcPath)) {
|
|
63
|
+
return {
|
|
64
|
+
module: module.name,
|
|
65
|
+
success: false,
|
|
66
|
+
action: 'skipped',
|
|
67
|
+
message: `Source path not found: ${mapping.src}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const srcStat = fs.statSync(srcPath);
|
|
71
|
+
if (srcStat.isDirectory()) {
|
|
72
|
+
copyRecursive(srcPath, destPath, state, module.name, hqRoot, filesChanged);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// Single file
|
|
76
|
+
const destDir = path.dirname(destPath);
|
|
77
|
+
if (!fs.existsSync(destDir)) {
|
|
78
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
const relativeDest = path.relative(hqRoot, destPath).replace(/\\/g, '/');
|
|
81
|
+
const newHash = hashFile(srcPath);
|
|
82
|
+
if (fs.existsSync(destPath)) {
|
|
83
|
+
const existingHash = hashFile(destPath);
|
|
84
|
+
const lastSyncedHash = state.files[relativeDest]?.hash;
|
|
85
|
+
if (lastSyncedHash && existingHash !== lastSyncedHash && existingHash !== newHash) {
|
|
86
|
+
console.warn(` Conflict: ${relativeDest} has local changes, skipping`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (existingHash === newHash) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
fs.copyFileSync(srcPath, destPath);
|
|
94
|
+
filesChanged.count++;
|
|
95
|
+
state.files[relativeDest] = {
|
|
96
|
+
hash: newHash,
|
|
97
|
+
syncedAt: new Date().toISOString(),
|
|
98
|
+
fromModule: module.name,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
writeState(hqRoot, state);
|
|
103
|
+
return {
|
|
104
|
+
module: module.name,
|
|
105
|
+
success: true,
|
|
106
|
+
action: 'synced',
|
|
107
|
+
filesChanged: filesChanged.count,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/strategies/merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background sync worker for "hq sync start".
|
|
3
|
+
*
|
|
4
|
+
* Forked as a detached child process. Polls for changes at a configurable
|
|
5
|
+
* interval and runs a full bidirectional sync each cycle.
|
|
6
|
+
*
|
|
7
|
+
* Usage (internal — called by cloud.ts):
|
|
8
|
+
* node sync-worker.js <hqRoot> <intervalMs>
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=sync-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-worker.d.ts","sourceRoot":"","sources":["../src/sync-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background sync worker for "hq sync start".
|
|
3
|
+
*
|
|
4
|
+
* Forked as a detached child process. Polls for changes at a configurable
|
|
5
|
+
* interval and runs a full bidirectional sync each cycle.
|
|
6
|
+
*
|
|
7
|
+
* Usage (internal — called by cloud.ts):
|
|
8
|
+
* node sync-worker.js <hqRoot> <intervalMs>
|
|
9
|
+
*/
|
|
10
|
+
import { fullSync, readSyncState, writeSyncState, computeLocalManifest } from './utils/sync.js';
|
|
11
|
+
const hqRoot = process.argv[2];
|
|
12
|
+
const intervalMs = parseInt(process.argv[3] ?? '30000', 10);
|
|
13
|
+
if (!hqRoot) {
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
/** Run one sync cycle. */
|
|
17
|
+
async function syncCycle() {
|
|
18
|
+
try {
|
|
19
|
+
const result = await fullSync(hqRoot);
|
|
20
|
+
// Update state
|
|
21
|
+
const manifest = computeLocalManifest(hqRoot);
|
|
22
|
+
const state = readSyncState(hqRoot);
|
|
23
|
+
state.running = true;
|
|
24
|
+
state.pid = process.pid;
|
|
25
|
+
state.lastSync = new Date().toISOString();
|
|
26
|
+
state.fileCount = manifest.length;
|
|
27
|
+
state.errors = result.errors;
|
|
28
|
+
writeSyncState(hqRoot, state);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Log errors to state, but keep running
|
|
32
|
+
try {
|
|
33
|
+
const state = readSyncState(hqRoot);
|
|
34
|
+
state.errors = ['Sync cycle failed — will retry next interval'];
|
|
35
|
+
writeSyncState(hqRoot, state);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Can't even write state — just continue
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Main loop. */
|
|
43
|
+
async function run() {
|
|
44
|
+
// Handle graceful shutdown
|
|
45
|
+
process.on('SIGTERM', () => {
|
|
46
|
+
try {
|
|
47
|
+
const state = readSyncState(hqRoot);
|
|
48
|
+
state.running = false;
|
|
49
|
+
state.pid = undefined;
|
|
50
|
+
writeSyncState(hqRoot, state);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Best-effort cleanup
|
|
54
|
+
}
|
|
55
|
+
process.exit(0);
|
|
56
|
+
});
|
|
57
|
+
process.on('SIGINT', () => {
|
|
58
|
+
try {
|
|
59
|
+
const state = readSyncState(hqRoot);
|
|
60
|
+
state.running = false;
|
|
61
|
+
state.pid = undefined;
|
|
62
|
+
writeSyncState(hqRoot, state);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Best-effort cleanup
|
|
66
|
+
}
|
|
67
|
+
process.exit(0);
|
|
68
|
+
});
|
|
69
|
+
// Run first sync immediately
|
|
70
|
+
await syncCycle();
|
|
71
|
+
// Then poll at the configured interval
|
|
72
|
+
setInterval(() => {
|
|
73
|
+
void syncCycle();
|
|
74
|
+
}, intervalMs);
|
|
75
|
+
}
|
|
76
|
+
void run();
|
|
77
|
+
//# sourceMappingURL=sync-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-worker.js","sourceRoot":"","sources":["../src/sync-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEhG,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC/B,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,EAAE,CAAC,CAAC;AAE5D,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,0BAA0B;AAC1B,KAAK,UAAU,SAAS;IACtB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;QAEtC,eAAe;QACf,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACpC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,KAAK,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;QACxB,KAAK,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC1C,KAAK,CAAC,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC;QAClC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7B,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;QACxC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;YACpC,KAAK,CAAC,MAAM,GAAG,CAAC,8CAA8C,CAAC,CAAC;YAChE,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,yCAAyC;QAC3C,CAAC;IACH,CAAC;AACH,CAAC;AAED,iBAAiB;AACjB,KAAK,UAAU,GAAG;IAChB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;YACpC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;YACtB,KAAK,CAAC,GAAG,GAAG,SAAS,CAAC;YACtB,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,sBAAsB;QACxB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;YACpC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;YACtB,KAAK,CAAC,GAAG,GAAG,SAAS,CAAC;YACtB,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,sBAAsB;QACxB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,6BAA6B;IAC7B,MAAM,SAAS,EAAE,CAAC;IAElB,uCAAuC;IACvC,WAAW,CAAC,GAAG,EAAE;QACf,KAAK,SAAS,EAAE,CAAC;IACnB,CAAC,EAAE,UAAU,CAAC,CAAC;AACjB,CAAC;AAED,KAAK,GAAG,EAAE,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HQ Module Manifest Types (US-001)
|
|
3
|
+
*/
|
|
4
|
+
export type SyncStrategy = 'link' | 'merge' | 'copy';
|
|
5
|
+
export type AccessLevel = 'public' | 'team' | `role:${string}`;
|
|
6
|
+
export interface PathMapping {
|
|
7
|
+
src: string;
|
|
8
|
+
dest: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ModuleDefinition {
|
|
11
|
+
name: string;
|
|
12
|
+
repo: string;
|
|
13
|
+
branch?: string;
|
|
14
|
+
strategy: SyncStrategy;
|
|
15
|
+
paths: PathMapping[];
|
|
16
|
+
access?: AccessLevel;
|
|
17
|
+
}
|
|
18
|
+
export interface ModulesManifest {
|
|
19
|
+
version: '1';
|
|
20
|
+
modules: ModuleDefinition[];
|
|
21
|
+
}
|
|
22
|
+
export interface ModuleLock {
|
|
23
|
+
version: '1';
|
|
24
|
+
locked: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
export interface SyncState {
|
|
27
|
+
version: '1';
|
|
28
|
+
files: Record<string, {
|
|
29
|
+
hash: string;
|
|
30
|
+
syncedAt: string;
|
|
31
|
+
fromModule: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export interface SyncResult {
|
|
35
|
+
module: string;
|
|
36
|
+
success: boolean;
|
|
37
|
+
action: 'cloned' | 'fetched' | 'synced' | 'skipped';
|
|
38
|
+
message?: string;
|
|
39
|
+
filesChanged?: number;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,MAAM,EAAE,CAAC;AAE/D,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,YAAY,CAAC;IACvB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,GAAG,CAAC;IACb,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,GAAG,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,GAAG,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client for hq-cloud.
|
|
3
|
+
* Reads stored credentials and attaches Authorization header to all requests.
|
|
4
|
+
*
|
|
5
|
+
* Base URL resolution order:
|
|
6
|
+
* 1. HQ_CLOUD_API_URL environment variable
|
|
7
|
+
* 2. ~/.hq/config.json "apiUrl" field
|
|
8
|
+
* 3. Default production URL
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the hq-cloud API base URL.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getApiUrl(): string;
|
|
14
|
+
/** Standard response shape from the API */
|
|
15
|
+
export interface ApiResponse<T = unknown> {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
status: number;
|
|
18
|
+
data?: T;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Make an authenticated request to the hq-cloud API.
|
|
23
|
+
* Throws if not logged in. Returns parsed JSON response.
|
|
24
|
+
*/
|
|
25
|
+
export declare function apiRequest<T = unknown>(method: string, urlPath: string, body?: unknown): Promise<ApiResponse<T>>;
|
|
26
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/utils/api-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAqBlC;AAED,2CAA2C;AAC3C,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,CAAC,GAAG,OAAO,EAC1C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO,GACb,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CA+CzB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client for hq-cloud.
|
|
3
|
+
* Reads stored credentials and attaches Authorization header to all requests.
|
|
4
|
+
*
|
|
5
|
+
* Base URL resolution order:
|
|
6
|
+
* 1. HQ_CLOUD_API_URL environment variable
|
|
7
|
+
* 2. ~/.hq/config.json "apiUrl" field
|
|
8
|
+
* 3. Default production URL
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import * as os from 'os';
|
|
13
|
+
import { readCredentials } from './credentials.js';
|
|
14
|
+
/** Default API base URL (production) */
|
|
15
|
+
const DEFAULT_API_URL = 'https://api.hq.indigoai.com';
|
|
16
|
+
/** Path to optional config file */
|
|
17
|
+
const CONFIG_PATH = path.join(os.homedir(), '.hq', 'config.json');
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the hq-cloud API base URL.
|
|
20
|
+
*/
|
|
21
|
+
export function getApiUrl() {
|
|
22
|
+
// 1. Environment variable takes precedence
|
|
23
|
+
if (process.env['HQ_CLOUD_API_URL']) {
|
|
24
|
+
return process.env['HQ_CLOUD_API_URL'].replace(/\/+$/, '');
|
|
25
|
+
}
|
|
26
|
+
// 2. Config file
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
29
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
30
|
+
const config = JSON.parse(raw);
|
|
31
|
+
if (config.apiUrl) {
|
|
32
|
+
return config.apiUrl.replace(/\/+$/, '');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Ignore config read errors
|
|
38
|
+
}
|
|
39
|
+
// 3. Default
|
|
40
|
+
return DEFAULT_API_URL;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Make an authenticated request to the hq-cloud API.
|
|
44
|
+
* Throws if not logged in. Returns parsed JSON response.
|
|
45
|
+
*/
|
|
46
|
+
export async function apiRequest(method, urlPath, body) {
|
|
47
|
+
const creds = readCredentials();
|
|
48
|
+
if (!creds) {
|
|
49
|
+
throw new Error('Not logged in. Run "hq auth login" first.');
|
|
50
|
+
}
|
|
51
|
+
const baseUrl = getApiUrl();
|
|
52
|
+
const url = `${baseUrl}${urlPath.startsWith('/') ? urlPath : '/' + urlPath}`;
|
|
53
|
+
const headers = {
|
|
54
|
+
'Authorization': `Bearer ${creds.token}`,
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
};
|
|
57
|
+
const fetchOptions = {
|
|
58
|
+
method,
|
|
59
|
+
headers,
|
|
60
|
+
};
|
|
61
|
+
if (body !== undefined && method !== 'GET') {
|
|
62
|
+
fetchOptions.body = JSON.stringify(body);
|
|
63
|
+
}
|
|
64
|
+
const response = await fetch(url, fetchOptions);
|
|
65
|
+
let data;
|
|
66
|
+
try {
|
|
67
|
+
data = await response.json();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Response may not be JSON
|
|
71
|
+
}
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
status: response.status,
|
|
76
|
+
error: data?.message
|
|
77
|
+
?? data?.error
|
|
78
|
+
?? `HTTP ${response.status}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
status: response.status,
|
|
84
|
+
data,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/utils/api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage for HQ CLI authentication.
|
|
3
|
+
* Stores Clerk auth tokens in ~/.hq/credentials.json.
|
|
4
|
+
*/
|
|
5
|
+
/** Stored credential shape */
|
|
6
|
+
export interface HqCredentials {
|
|
7
|
+
/** Clerk session token (JWT) */
|
|
8
|
+
token: string;
|
|
9
|
+
/** Clerk user ID */
|
|
10
|
+
userId: string;
|
|
11
|
+
/** User's email (for display) */
|
|
12
|
+
email?: string;
|
|
13
|
+
/** When the token was stored (ISO string) */
|
|
14
|
+
storedAt: string;
|
|
15
|
+
/** When the token expires (ISO string, if known) */
|
|
16
|
+
expiresAt?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Set the base directory for config files. Intended for testing only.
|
|
20
|
+
*/
|
|
21
|
+
export declare function _setConfigHome(dir: string | null): void;
|
|
22
|
+
/**
|
|
23
|
+
* Read stored credentials. Returns null if not logged in or file is missing/corrupt.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readCredentials(): HqCredentials | null;
|
|
26
|
+
/**
|
|
27
|
+
* Write credentials to disk. Creates ~/.hq if needed.
|
|
28
|
+
* File permissions are set to owner-only (0o600).
|
|
29
|
+
*/
|
|
30
|
+
export declare function writeCredentials(creds: HqCredentials): void;
|
|
31
|
+
/**
|
|
32
|
+
* Clear stored credentials (logout).
|
|
33
|
+
* Returns true if credentials were removed, false if none existed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function clearCredentials(): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Get the credentials file path (for display/debugging).
|
|
38
|
+
*/
|
|
39
|
+
export declare function getCredentialsPath(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Check if credentials are expired (if expiresAt is set).
|
|
42
|
+
*/
|
|
43
|
+
export declare function isExpired(creds: HqCredentials): boolean;
|
|
44
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/utils/credentials.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,8BAA8B;AAC9B,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AASD;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEvD;AA8BD;;GAEG;AACH,wBAAgB,eAAe,IAAI,aAAa,GAAG,IAAI,CAetD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAI3D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAO1C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAKvD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage for HQ CLI authentication.
|
|
3
|
+
* Stores Clerk auth tokens in ~/.hq/credentials.json.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
/**
|
|
9
|
+
* Override for the config directory base path.
|
|
10
|
+
* Set via HQ_CONFIG_HOME env var or _setConfigHome (for testing).
|
|
11
|
+
* When null, defaults to os.homedir().
|
|
12
|
+
*/
|
|
13
|
+
let configHomeOverride = null;
|
|
14
|
+
/**
|
|
15
|
+
* Set the base directory for config files. Intended for testing only.
|
|
16
|
+
*/
|
|
17
|
+
export function _setConfigHome(dir) {
|
|
18
|
+
configHomeOverride = dir;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the ~/.hq config directory path.
|
|
22
|
+
* Respects HQ_CONFIG_HOME env var, _setConfigHome override, or defaults to ~/.hq.
|
|
23
|
+
*/
|
|
24
|
+
function getConfigDir() {
|
|
25
|
+
const base = configHomeOverride
|
|
26
|
+
?? process.env['HQ_CONFIG_HOME']
|
|
27
|
+
?? os.homedir();
|
|
28
|
+
return path.join(base, '.hq');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the credentials file path.
|
|
32
|
+
*/
|
|
33
|
+
function getCredentialsFilePath() {
|
|
34
|
+
return path.join(getConfigDir(), 'credentials.json');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Ensure the config directory exists with restricted permissions.
|
|
38
|
+
*/
|
|
39
|
+
function ensureConfigDir() {
|
|
40
|
+
const dir = getConfigDir();
|
|
41
|
+
if (!fs.existsSync(dir)) {
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Read stored credentials. Returns null if not logged in or file is missing/corrupt.
|
|
47
|
+
*/
|
|
48
|
+
export function readCredentials() {
|
|
49
|
+
try {
|
|
50
|
+
const credPath = getCredentialsFilePath();
|
|
51
|
+
if (!fs.existsSync(credPath)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const raw = fs.readFileSync(credPath, 'utf-8');
|
|
55
|
+
const creds = JSON.parse(raw);
|
|
56
|
+
if (!creds.token || !creds.userId) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return creds;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Write credentials to disk. Creates ~/.hq if needed.
|
|
67
|
+
* File permissions are set to owner-only (0o600).
|
|
68
|
+
*/
|
|
69
|
+
export function writeCredentials(creds) {
|
|
70
|
+
ensureConfigDir();
|
|
71
|
+
const content = JSON.stringify(creds, null, 2);
|
|
72
|
+
fs.writeFileSync(getCredentialsFilePath(), content, { mode: 0o600 });
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Clear stored credentials (logout).
|
|
76
|
+
* Returns true if credentials were removed, false if none existed.
|
|
77
|
+
*/
|
|
78
|
+
export function clearCredentials() {
|
|
79
|
+
const credPath = getCredentialsFilePath();
|
|
80
|
+
if (!fs.existsSync(credPath)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
fs.unlinkSync(credPath);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get the credentials file path (for display/debugging).
|
|
88
|
+
*/
|
|
89
|
+
export function getCredentialsPath() {
|
|
90
|
+
return getCredentialsFilePath();
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if credentials are expired (if expiresAt is set).
|
|
94
|
+
*/
|
|
95
|
+
export function isExpired(creds) {
|
|
96
|
+
if (!creds.expiresAt) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return new Date(creds.expiresAt) <= new Date();
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/utils/credentials.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAgBzB;;;;GAIG;AACH,IAAI,kBAAkB,GAAkB,IAAI,CAAC;AAE7C;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C,kBAAkB,GAAG,GAAG,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY;IACnB,MAAM,IAAI,GAAG,kBAAkB;WAC1B,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;WAC7B,EAAE,CAAC,OAAO,EAAE,CAAC;IAClB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,SAAS,sBAAsB;IAC7B,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,kBAAkB,CAAC,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,SAAS,eAAe;IACtB,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;QAC1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;QAC/C,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAoB;IACnD,eAAe,EAAE,CAAC;IAClB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/C,EAAE,CAAC,aAAa,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACvE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;IAC1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,sBAAsB,EAAE,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,KAAoB;IAC5C,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function cloneRepo(repoUrl: string, targetDir: string, branch?: string): Promise<void>;
|
|
2
|
+
export declare function fetchRepo(repoDir: string): Promise<void>;
|
|
3
|
+
export declare function pullRepo(repoDir: string): Promise<void>;
|
|
4
|
+
export declare function getCurrentCommit(repoDir: string): Promise<string>;
|
|
5
|
+
export declare function checkoutCommit(repoDir: string, commitSha: string): Promise<void>;
|
|
6
|
+
export declare function isRepo(dir: string): Promise<boolean>;
|
|
7
|
+
export declare function getRemoteUrl(repoDir: string): Promise<string | null>;
|
|
8
|
+
export declare function isBehindRemote(repoDir: string): Promise<{
|
|
9
|
+
behind: boolean;
|
|
10
|
+
commits: number;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function ensureGitignore(hqRoot: string, entry: string): void;
|
|
13
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAIA,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlG;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9D;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG7D;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAIvE;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtF;AAED,wBAAsB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ1D;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS1E;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CASnG;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAUnE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { simpleGit } from 'simple-git';
|
|
4
|
+
export async function cloneRepo(repoUrl, targetDir, branch) {
|
|
5
|
+
const git = simpleGit();
|
|
6
|
+
const options = branch ? ['--branch', branch] : [];
|
|
7
|
+
await git.clone(repoUrl, targetDir, options);
|
|
8
|
+
}
|
|
9
|
+
export async function fetchRepo(repoDir) {
|
|
10
|
+
const git = simpleGit(repoDir);
|
|
11
|
+
await git.fetch(['--all']);
|
|
12
|
+
}
|
|
13
|
+
export async function pullRepo(repoDir) {
|
|
14
|
+
const git = simpleGit(repoDir);
|
|
15
|
+
await git.pull();
|
|
16
|
+
}
|
|
17
|
+
export async function getCurrentCommit(repoDir) {
|
|
18
|
+
const git = simpleGit(repoDir);
|
|
19
|
+
const log = await git.log({ maxCount: 1 });
|
|
20
|
+
return log.latest?.hash ?? '';
|
|
21
|
+
}
|
|
22
|
+
export async function checkoutCommit(repoDir, commitSha) {
|
|
23
|
+
const git = simpleGit(repoDir);
|
|
24
|
+
await git.checkout(commitSha);
|
|
25
|
+
}
|
|
26
|
+
export async function isRepo(dir) {
|
|
27
|
+
if (!fs.existsSync(dir))
|
|
28
|
+
return false;
|
|
29
|
+
try {
|
|
30
|
+
const git = simpleGit(dir);
|
|
31
|
+
return await git.checkIsRepo();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function getRemoteUrl(repoDir) {
|
|
38
|
+
try {
|
|
39
|
+
const git = simpleGit(repoDir);
|
|
40
|
+
const remotes = await git.getRemotes(true);
|
|
41
|
+
const origin = remotes.find(r => r.name === 'origin');
|
|
42
|
+
return origin?.refs?.fetch ?? null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function isBehindRemote(repoDir) {
|
|
49
|
+
try {
|
|
50
|
+
const git = simpleGit(repoDir);
|
|
51
|
+
await git.fetch();
|
|
52
|
+
const status = await git.status();
|
|
53
|
+
return { behind: status.behind > 0, commits: status.behind };
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return { behind: false, commits: 0 };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function ensureGitignore(hqRoot, entry) {
|
|
60
|
+
const gitignorePath = path.join(hqRoot, '.gitignore');
|
|
61
|
+
let content = '';
|
|
62
|
+
if (fs.existsSync(gitignorePath)) {
|
|
63
|
+
content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
64
|
+
}
|
|
65
|
+
if (!content.includes(entry)) {
|
|
66
|
+
content = content.trimEnd() + '\n' + entry + '\n';
|
|
67
|
+
fs.writeFileSync(gitignorePath, content);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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"}
|