@ongtrieuhau861457/runner-tailscale-sync 1.260202.11920
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/README.md +310 -0
- package/bin/runner-sync.js +85 -0
- package/package.json +41 -0
- package/src/adapters/fs.js +160 -0
- package/src/adapters/git.js +185 -0
- package/src/adapters/http.js +56 -0
- package/src/adapters/process.js +151 -0
- package/src/adapters/ssh.js +103 -0
- package/src/adapters/tailscale.js +406 -0
- package/src/cli/commands/init.js +13 -0
- package/src/cli/commands/push.js +13 -0
- package/src/cli/commands/status.js +13 -0
- package/src/cli/commands/sync.js +22 -0
- package/src/cli/parser.js +114 -0
- package/src/core/data-sync.js +177 -0
- package/src/core/init.js +141 -0
- package/src/core/push.js +113 -0
- package/src/core/runner-detector.js +167 -0
- package/src/core/service-controller.js +141 -0
- package/src/core/status.js +130 -0
- package/src/core/sync-orchestrator.js +260 -0
- package/src/index.js +140 -0
- package/src/utils/config.js +129 -0
- package/src/utils/constants.js +33 -0
- package/src/utils/errors.js +45 -0
- package/src/utils/logger.js +154 -0
- package/src/utils/time.js +65 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/service-controller.js
|
|
3
|
+
* Stop/Start services trên runners qua SSH
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ssh = require("../adapters/ssh");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse input
|
|
10
|
+
*/
|
|
11
|
+
function parseInput(config, previousRunner, logger) {
|
|
12
|
+
return {
|
|
13
|
+
remoteHost: previousRunner?.dnsName || previousRunner?.ips?.[0],
|
|
14
|
+
services: config.servicesToStop,
|
|
15
|
+
sshPath: config.sshPath,
|
|
16
|
+
logger,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validate
|
|
22
|
+
*/
|
|
23
|
+
function validate(input) {
|
|
24
|
+
const errors = [];
|
|
25
|
+
|
|
26
|
+
if (!input.remoteHost) {
|
|
27
|
+
errors.push("Remote host is required");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!input.services || input.services.length === 0) {
|
|
31
|
+
errors.push("No services specified to stop");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Plan
|
|
39
|
+
*/
|
|
40
|
+
function plan(input) {
|
|
41
|
+
return {
|
|
42
|
+
action: "stop_remote_services",
|
|
43
|
+
host: input.remoteHost,
|
|
44
|
+
services: input.services,
|
|
45
|
+
sshPath: input.sshPath,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Execute - stop services
|
|
51
|
+
*/
|
|
52
|
+
async function execute(planResult, input) {
|
|
53
|
+
const { logger } = input;
|
|
54
|
+
|
|
55
|
+
logger.info(`Stopping services on ${planResult.host}...`);
|
|
56
|
+
|
|
57
|
+
// Check SSH connection first
|
|
58
|
+
const connected = ssh.checkConnection(planResult.host, {
|
|
59
|
+
logger,
|
|
60
|
+
sshPath: planResult.sshPath,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!connected) {
|
|
64
|
+
logger.warn(`Cannot connect to ${planResult.host} via SSH - services may still be running`);
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
stoppedServices: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Stop services
|
|
72
|
+
await ssh.stopServices(planResult.host, planResult.services, {
|
|
73
|
+
logger,
|
|
74
|
+
sshPath: planResult.sshPath,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
stoppedServices: planResult.services,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Report
|
|
85
|
+
*/
|
|
86
|
+
function report(result, input) {
|
|
87
|
+
const { logger } = input;
|
|
88
|
+
|
|
89
|
+
if (result.success) {
|
|
90
|
+
logger.success(`Stopped ${result.stoppedServices.length} services`);
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
stoppedServices: result.stoppedServices,
|
|
94
|
+
};
|
|
95
|
+
} else {
|
|
96
|
+
logger.warn("Failed to stop some services");
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
stoppedServices: [],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Main stop services function
|
|
106
|
+
*/
|
|
107
|
+
async function stopRemoteServices(config, previousRunner, logger) {
|
|
108
|
+
if (!previousRunner) {
|
|
109
|
+
logger.info("No previous runner - skipping service stop");
|
|
110
|
+
return { success: true, stoppedServices: [] };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Step 1: Parse Input
|
|
114
|
+
const input = parseInput(config, previousRunner, logger);
|
|
115
|
+
|
|
116
|
+
// Step 2: Validate
|
|
117
|
+
const errors = validate(input);
|
|
118
|
+
if (errors.length > 0) {
|
|
119
|
+
// Not critical - just warn
|
|
120
|
+
logger.warn(`Service stop validation: ${errors.join(", ")}`);
|
|
121
|
+
return { success: true, stoppedServices: [] };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Step 3: Plan
|
|
125
|
+
const planResult = plan(input);
|
|
126
|
+
|
|
127
|
+
// Step 4: Execute
|
|
128
|
+
const execResult = await execute(planResult, input);
|
|
129
|
+
|
|
130
|
+
// Step 5: Report
|
|
131
|
+
return report(execResult, input);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
stopRemoteServices,
|
|
136
|
+
parseInput,
|
|
137
|
+
validate,
|
|
138
|
+
plan,
|
|
139
|
+
execute,
|
|
140
|
+
report,
|
|
141
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/status.js
|
|
3
|
+
* Show Tailscale status and runner info
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const tailscale = require("../adapters/tailscale");
|
|
7
|
+
const fs_adapter = require("../adapters/fs");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse input
|
|
11
|
+
*/
|
|
12
|
+
function parseInput(config, logger) {
|
|
13
|
+
return {
|
|
14
|
+
tailscaleEnable: config.tailscaleEnable,
|
|
15
|
+
tailscaleTags: config.tailscaleTags,
|
|
16
|
+
runnerDataDir: config.runnerDataDir,
|
|
17
|
+
logger,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate
|
|
23
|
+
*/
|
|
24
|
+
function validate() {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Plan
|
|
30
|
+
*/
|
|
31
|
+
function plan() {
|
|
32
|
+
return {
|
|
33
|
+
action: "status",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute
|
|
39
|
+
*/
|
|
40
|
+
async function execute(planResult, input) {
|
|
41
|
+
const { logger } = input;
|
|
42
|
+
|
|
43
|
+
const result = {
|
|
44
|
+
tailscale: null,
|
|
45
|
+
peers: [],
|
|
46
|
+
peerCount: 0,
|
|
47
|
+
runnerData: null,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (input.tailscaleEnable) {
|
|
51
|
+
const status = tailscale.getStatus(logger);
|
|
52
|
+
result.tailscale = status || null;
|
|
53
|
+
|
|
54
|
+
if (status) {
|
|
55
|
+
result.peerCount = status.Peer ? Object.keys(status.Peer).length : 0;
|
|
56
|
+
result.peers = tailscale.findPeersWithTag(input.tailscaleTags, logger);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (fs_adapter.exists(input.runnerDataDir)) {
|
|
61
|
+
result.runnerData = {
|
|
62
|
+
path: input.runnerDataDir,
|
|
63
|
+
size: fs_adapter.getDirSize(input.runnerDataDir),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Report
|
|
72
|
+
*/
|
|
73
|
+
function report(result, input) {
|
|
74
|
+
const { logger } = input;
|
|
75
|
+
|
|
76
|
+
if (input.tailscaleEnable) {
|
|
77
|
+
if (result.tailscale) {
|
|
78
|
+
logger.info("━━━ Tailscale Status ━━━");
|
|
79
|
+
logger.info(`Backend: ${result.tailscale.BackendState}`);
|
|
80
|
+
|
|
81
|
+
if (result.tailscale.Self) {
|
|
82
|
+
logger.info(`Hostname: ${result.tailscale.Self.HostName || "N/A"}`);
|
|
83
|
+
logger.info(`DNS: ${result.tailscale.Self.DNSName || "N/A"}`);
|
|
84
|
+
logger.info(`IPs: ${result.tailscale.Self.TailscaleIPs?.join(", ") || "N/A"}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (result.peers.length > 0) {
|
|
88
|
+
logger.info(`Peers: ${result.peerCount} connected`);
|
|
89
|
+
logger.info(`Peers with tag '${input.tailscaleTags}':`);
|
|
90
|
+
result.peers.forEach((peer, i) => {
|
|
91
|
+
logger.info(` ${i + 1}. ${peer.hostname} (${peer.ips[0]})`);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
logger.warn("Tailscale not connected");
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
logger.info("Tailscale disabled");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
logger.info("━━━ Runner Data ━━━");
|
|
102
|
+
if (result.runnerData) {
|
|
103
|
+
logger.info(`Directory: ${result.runnerData.path}`);
|
|
104
|
+
logger.info(`Size: ${fs_adapter.formatBytes(result.runnerData.size)}`);
|
|
105
|
+
} else {
|
|
106
|
+
logger.warn(`Directory not found: ${input.runnerDataDir}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { success: true };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Main status function
|
|
114
|
+
*/
|
|
115
|
+
async function showStatus(config, logger) {
|
|
116
|
+
const input = parseInput(config, logger);
|
|
117
|
+
validate(input);
|
|
118
|
+
const planResult = plan(input);
|
|
119
|
+
const execResult = await execute(planResult, input);
|
|
120
|
+
return report(execResult, input);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
showStatus,
|
|
125
|
+
parseInput,
|
|
126
|
+
validate,
|
|
127
|
+
plan,
|
|
128
|
+
execute,
|
|
129
|
+
report,
|
|
130
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/sync-orchestrator.js
|
|
3
|
+
* Điều phối toàn bộ quy trình sync
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const tailscale = require("../adapters/tailscale");
|
|
7
|
+
const git = require("../adapters/git");
|
|
8
|
+
const fs_adapter = require("../adapters/fs");
|
|
9
|
+
const runnerDetector = require("./runner-detector");
|
|
10
|
+
const dataSync = require("./data-sync");
|
|
11
|
+
const serviceController = require("./service-controller");
|
|
12
|
+
const { getTimestamp } = require("../utils/time");
|
|
13
|
+
const { ValidationError, ProcessError } = require("../utils/errors");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse input
|
|
17
|
+
*/
|
|
18
|
+
function parseInput(config, logger) {
|
|
19
|
+
return {
|
|
20
|
+
config,
|
|
21
|
+
logger,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate
|
|
27
|
+
*/
|
|
28
|
+
function validate(input) {
|
|
29
|
+
const errors = input.config.validate();
|
|
30
|
+
if (errors.length > 0) {
|
|
31
|
+
throw new ValidationError(`Validation failed:\n - ${errors.join("\n - ")}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Plan - xác định các bước cần thực hiện
|
|
37
|
+
*/
|
|
38
|
+
function plan(input) {
|
|
39
|
+
const { config } = input;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
steps: [
|
|
43
|
+
{ name: "setup_directories", enabled: true },
|
|
44
|
+
{ name: "connect_tailscale", enabled: config.tailscaleEnable },
|
|
45
|
+
{ name: "detect_previous_runner", enabled: config.tailscaleEnable },
|
|
46
|
+
{ name: "pull_data", enabled: config.tailscaleEnable },
|
|
47
|
+
{ name: "stop_remote_services", enabled: config.tailscaleEnable },
|
|
48
|
+
{ name: "push_to_git", enabled: config.gitEnabled },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Execute - chạy từng bước
|
|
55
|
+
*/
|
|
56
|
+
async function execute(planResult, input) {
|
|
57
|
+
const { config, logger } = input;
|
|
58
|
+
const results = {};
|
|
59
|
+
|
|
60
|
+
for (const step of planResult.steps) {
|
|
61
|
+
if (!step.enabled) {
|
|
62
|
+
logger.debug(`Skipping step: ${step.name}`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
logger.info(`━━━ Step: ${step.name} ━━━`);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
switch (step.name) {
|
|
70
|
+
case "setup_directories":
|
|
71
|
+
results.setupDirs = await setupDirectories(config, logger);
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case "connect_tailscale":
|
|
75
|
+
results.tailscale = await connectTailscale(config, logger);
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case "detect_previous_runner":
|
|
79
|
+
results.detection = await runnerDetector.detectPreviousRunner(config, logger);
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case "pull_data":
|
|
83
|
+
results.pullData = await dataSync.pullData(
|
|
84
|
+
config,
|
|
85
|
+
results.detection?.previousRunner,
|
|
86
|
+
logger
|
|
87
|
+
);
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case "stop_remote_services":
|
|
91
|
+
results.stopServices = await serviceController.stopRemoteServices(
|
|
92
|
+
config,
|
|
93
|
+
results.detection?.previousRunner,
|
|
94
|
+
logger
|
|
95
|
+
);
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
case "push_to_git":
|
|
99
|
+
results.pushGit = await pushToGit(config, logger);
|
|
100
|
+
break;
|
|
101
|
+
|
|
102
|
+
default:
|
|
103
|
+
logger.warn(`Unknown step: ${step.name}`);
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
logger.error(`Step failed: ${step.name} - ${err.message}`);
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Report
|
|
116
|
+
*/
|
|
117
|
+
function report(results, input) {
|
|
118
|
+
const { logger } = input;
|
|
119
|
+
|
|
120
|
+
logger.info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
121
|
+
logger.success("Sync orchestration completed!");
|
|
122
|
+
|
|
123
|
+
if (results.tailscale) {
|
|
124
|
+
logger.info(`Tailscale IP: ${results.tailscale.ip || "N/A"}`);
|
|
125
|
+
logger.info(`Tailscale Hostname: ${results.tailscale.hostname || "N/A"}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (results.detection?.previousRunner) {
|
|
129
|
+
logger.info(`Previous runner: ${results.detection.previousRunner.hostname}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (results.pullData?.syncedSize) {
|
|
133
|
+
logger.info(`Synced data: ${fs_adapter.formatBytes(results.pullData.syncedSize)}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (results.stopServices?.stoppedServices?.length > 0) {
|
|
137
|
+
logger.info(`Stopped services: ${results.stopServices.stoppedServices.join(", ")}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
logger.info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
success: true,
|
|
144
|
+
results,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Setup directories
|
|
150
|
+
*/
|
|
151
|
+
async function setupDirectories(config, logger) {
|
|
152
|
+
logger.info("Setting up directories...");
|
|
153
|
+
|
|
154
|
+
const dirs = config.getDirectoriesToEnsure();
|
|
155
|
+
fs_adapter.ensureDirs(dirs);
|
|
156
|
+
|
|
157
|
+
logger.success(`Created ${dirs.length} directories`);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
directories: dirs,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Connect to Tailscale
|
|
167
|
+
*/
|
|
168
|
+
async function connectTailscale(config, logger) {
|
|
169
|
+
logger.info("Connecting to Tailscale network...");
|
|
170
|
+
|
|
171
|
+
// Install if needed
|
|
172
|
+
const installed = tailscale.install(logger);
|
|
173
|
+
if (!installed) {
|
|
174
|
+
throw new ProcessError("Failed to install Tailscale");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Login
|
|
178
|
+
await tailscale.login(
|
|
179
|
+
config.tailscaleClientId,
|
|
180
|
+
config.tailscaleClientSecret,
|
|
181
|
+
config.tailscaleTags,
|
|
182
|
+
logger,
|
|
183
|
+
config
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Get connection info
|
|
187
|
+
const ip = tailscale.getIP(logger);
|
|
188
|
+
const hostname = tailscale.getHostname(logger);
|
|
189
|
+
|
|
190
|
+
logger.success(`Connected to Tailscale: ${ip || hostname}`);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
ip,
|
|
195
|
+
hostname,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Push to git
|
|
201
|
+
*/
|
|
202
|
+
async function pushToGit(config, logger) {
|
|
203
|
+
logger.info("Pushing data to git repository...");
|
|
204
|
+
|
|
205
|
+
if (!git.isAvailable()) {
|
|
206
|
+
logger.warn("Git not available - skipping push");
|
|
207
|
+
return { success: false };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!git.isGitRepo(config.cwd)) {
|
|
211
|
+
logger.warn("Not a git repository - skipping push");
|
|
212
|
+
return { success: false };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const timestamp = getTimestamp();
|
|
216
|
+
const message = `[runner-sync] Update .runner-data at ${timestamp}`;
|
|
217
|
+
|
|
218
|
+
const pushed = await git.commitAndPush(message, config.gitBranch, {
|
|
219
|
+
logger,
|
|
220
|
+
cwd: config.cwd,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (pushed) {
|
|
224
|
+
logger.success("Pushed to git repository");
|
|
225
|
+
return { success: true };
|
|
226
|
+
} else {
|
|
227
|
+
logger.info("No changes to push");
|
|
228
|
+
return { success: true, noChanges: true };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Main orchestrate function
|
|
234
|
+
*/
|
|
235
|
+
async function orchestrate(config, logger) {
|
|
236
|
+
// Step 1: Parse Input
|
|
237
|
+
const input = parseInput(config, logger);
|
|
238
|
+
|
|
239
|
+
// Step 2: Validate
|
|
240
|
+
validate(input);
|
|
241
|
+
|
|
242
|
+
// Step 3: Plan
|
|
243
|
+
const planResult = plan(input);
|
|
244
|
+
logger.debug(`Planned ${planResult.steps.filter(s => s.enabled).length} steps`);
|
|
245
|
+
|
|
246
|
+
// Step 4: Execute
|
|
247
|
+
const execResult = await execute(planResult, input);
|
|
248
|
+
|
|
249
|
+
// Step 5: Report
|
|
250
|
+
return report(execResult, input);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = {
|
|
254
|
+
orchestrate,
|
|
255
|
+
parseInput,
|
|
256
|
+
validate,
|
|
257
|
+
plan,
|
|
258
|
+
execute,
|
|
259
|
+
report,
|
|
260
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/index.js
|
|
3
|
+
* Main library export - cho phép import như library
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const Config = require("./utils/config");
|
|
7
|
+
const Logger = require("./utils/logger");
|
|
8
|
+
const syncOrchestrator = require("./core/sync-orchestrator");
|
|
9
|
+
const runnerDetector = require("./core/runner-detector");
|
|
10
|
+
const dataSync = require("./core/data-sync");
|
|
11
|
+
const serviceController = require("./core/service-controller");
|
|
12
|
+
const initRunner = require("./core/init");
|
|
13
|
+
const pushRunner = require("./core/push");
|
|
14
|
+
const statusRunner = require("./core/status");
|
|
15
|
+
|
|
16
|
+
// Adapters
|
|
17
|
+
const tailscale = require("./adapters/tailscale");
|
|
18
|
+
const git = require("./adapters/git");
|
|
19
|
+
const ssh = require("./adapters/ssh");
|
|
20
|
+
const fs_adapter = require("./adapters/fs");
|
|
21
|
+
const process_adapter = require("./adapters/process");
|
|
22
|
+
const http_adapter = require("./adapters/http");
|
|
23
|
+
|
|
24
|
+
// Utils
|
|
25
|
+
const time = require("./utils/time");
|
|
26
|
+
const errors = require("./utils/errors");
|
|
27
|
+
const constants = require("./utils/constants");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Main API - orchestrate full sync
|
|
31
|
+
*/
|
|
32
|
+
async function sync(options = {}) {
|
|
33
|
+
const config = new Config(options);
|
|
34
|
+
const pkg = require("../package.json");
|
|
35
|
+
|
|
36
|
+
const logger = new Logger({
|
|
37
|
+
packageName: pkg.name,
|
|
38
|
+
version: pkg.version,
|
|
39
|
+
command: "sync",
|
|
40
|
+
verbose: options.verbose || false,
|
|
41
|
+
quiet: options.quiet || false,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
logger.printBanner();
|
|
45
|
+
|
|
46
|
+
return await syncOrchestrator.orchestrate(config, logger);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Init only
|
|
51
|
+
*/
|
|
52
|
+
async function init(options = {}) {
|
|
53
|
+
const config = new Config(options);
|
|
54
|
+
const pkg = require("../package.json");
|
|
55
|
+
|
|
56
|
+
const logger = new Logger({
|
|
57
|
+
packageName: pkg.name,
|
|
58
|
+
version: pkg.version,
|
|
59
|
+
command: "init",
|
|
60
|
+
verbose: options.verbose || false,
|
|
61
|
+
quiet: options.quiet || false,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
logger.printBanner();
|
|
65
|
+
|
|
66
|
+
return await initRunner.initRunner(config, logger);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Push to git only
|
|
71
|
+
*/
|
|
72
|
+
async function push(options = {}) {
|
|
73
|
+
const config = new Config(options);
|
|
74
|
+
const pkg = require("../package.json");
|
|
75
|
+
|
|
76
|
+
const logger = new Logger({
|
|
77
|
+
packageName: pkg.name,
|
|
78
|
+
version: pkg.version,
|
|
79
|
+
command: "push",
|
|
80
|
+
verbose: options.verbose || false,
|
|
81
|
+
quiet: options.quiet || false,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
logger.printBanner();
|
|
85
|
+
|
|
86
|
+
return await pushRunner.pushRunnerData(config, logger);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Show status
|
|
91
|
+
*/
|
|
92
|
+
async function status(options = {}) {
|
|
93
|
+
const config = new Config(options);
|
|
94
|
+
const pkg = require("../package.json");
|
|
95
|
+
|
|
96
|
+
const logger = new Logger({
|
|
97
|
+
packageName: pkg.name,
|
|
98
|
+
version: pkg.version,
|
|
99
|
+
command: "status",
|
|
100
|
+
verbose: options.verbose || false,
|
|
101
|
+
quiet: options.quiet || false,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
logger.printBanner();
|
|
105
|
+
|
|
106
|
+
return await statusRunner.showStatus(config, logger);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Export API và modules
|
|
110
|
+
module.exports = {
|
|
111
|
+
// Main API
|
|
112
|
+
sync,
|
|
113
|
+
init,
|
|
114
|
+
push,
|
|
115
|
+
status,
|
|
116
|
+
|
|
117
|
+
// Core modules
|
|
118
|
+
syncOrchestrator,
|
|
119
|
+
runnerDetector,
|
|
120
|
+
dataSync,
|
|
121
|
+
serviceController,
|
|
122
|
+
initRunner,
|
|
123
|
+
pushRunner,
|
|
124
|
+
statusRunner,
|
|
125
|
+
|
|
126
|
+
// Adapters
|
|
127
|
+
tailscale,
|
|
128
|
+
git,
|
|
129
|
+
ssh,
|
|
130
|
+
fs: fs_adapter,
|
|
131
|
+
process: process_adapter,
|
|
132
|
+
http: http_adapter,
|
|
133
|
+
|
|
134
|
+
// Utils
|
|
135
|
+
Config,
|
|
136
|
+
Logger,
|
|
137
|
+
time,
|
|
138
|
+
errors,
|
|
139
|
+
constants,
|
|
140
|
+
};
|