Skip to content

Commit c658101

Browse files
committed
feat: add tianji worker pull command
1 parent c9ce2ab commit c658101

2 files changed

Lines changed: 243 additions & 1 deletion

File tree

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import * as fs from 'fs-extra';
2+
import * as path from 'path';
3+
import chalk from 'chalk';
4+
import ora from 'ora';
5+
import type { Arguments, CommandBuilder } from 'yargs';
6+
import {
7+
loadGlobalConfig,
8+
loadProjectConfig,
9+
saveProjectConfig,
10+
isLoggedIn,
11+
} from '../../utils/config.js';
12+
import { createAPIClient } from '../../utils/api.js';
13+
14+
interface PullOptions {
15+
workerId?: string;
16+
overwrite?: boolean;
17+
}
18+
19+
export const command = 'pull [worker-id]';
20+
export const desc = 'Pull worker code from remote Tianji server';
21+
22+
export const builder: CommandBuilder<PullOptions, PullOptions> = (yargs) => {
23+
return yargs
24+
.positional('worker-id', {
25+
type: 'string',
26+
describe: 'Worker ID to pull (optional if .tianjirc exists)',
27+
})
28+
.option('overwrite', {
29+
type: 'boolean',
30+
describe: 'Overwrite existing src/index.ts file',
31+
default: false,
32+
});
33+
};
34+
35+
export const handler = async (argv: Arguments<PullOptions>): Promise<void> => {
36+
console.log(chalk.cyan('\n📥 Pulling Tianji Worker from remote...\n'));
37+
38+
try {
39+
// Check if user is logged in
40+
if (!(await isLoggedIn())) {
41+
console.error(
42+
chalk.red('Error: Not logged in. Please run "tianji login" first.\n')
43+
);
44+
process.exit(1);
45+
}
46+
47+
// Load global config
48+
const globalConfig = await loadGlobalConfig();
49+
if (!globalConfig) {
50+
console.error(
51+
chalk.red(
52+
'Error: Configuration not found. Please run "tianji login" first.\n'
53+
)
54+
);
55+
process.exit(1);
56+
}
57+
58+
const currentDir = process.cwd();
59+
const projectConfig = await loadProjectConfig(currentDir);
60+
const hasTianjirc = projectConfig !== null;
61+
62+
// Determine worker ID
63+
let workerId: string | undefined;
64+
if (argv.workerId) {
65+
// Command line argument takes priority
66+
workerId = argv.workerId;
67+
} else if (hasTianjirc && projectConfig.workerId) {
68+
// Use worker ID from .tianjirc
69+
workerId = projectConfig.workerId;
70+
}
71+
72+
if (!workerId) {
73+
console.error(
74+
chalk.red(
75+
'Error: Worker ID is required. Please provide it as an argument or ensure .tianjirc contains a workerId.\n'
76+
)
77+
);
78+
console.log(chalk.white('Usage: tianji worker pull <worker-id>\n'));
79+
process.exit(1);
80+
}
81+
82+
// Check directory status
83+
const files = await fs.readdir(currentDir);
84+
const isEmptyDir = files.length === 0;
85+
86+
if (!hasTianjirc && !isEmptyDir) {
87+
// Directory is not empty and not a Tianji worker project
88+
console.error(
89+
chalk.red(
90+
'Error: Current directory is not empty and is not a Tianji worker project.\n'
91+
)
92+
);
93+
console.log(
94+
chalk.yellow(
95+
'⚠️ Warning: Please use an empty directory or navigate to an existing Tianji worker project.\n'
96+
)
97+
);
98+
process.exit(1);
99+
}
100+
101+
// Fetch worker from API
102+
const fetchSpinner = ora('Fetching worker from remote...').start();
103+
let worker;
104+
try {
105+
const apiClient = createAPIClient(globalConfig);
106+
worker = await apiClient.getWorker(workerId);
107+
108+
if (!worker) {
109+
fetchSpinner.fail(chalk.red('Worker not found'));
110+
console.error(
111+
chalk.red(`\nError: Worker with ID "${workerId}" not found.\n`)
112+
);
113+
process.exit(1);
114+
}
115+
116+
fetchSpinner.succeed(chalk.green('Worker fetched successfully'));
117+
} catch (error) {
118+
fetchSpinner.fail(chalk.red('Failed to fetch worker'));
119+
console.error(chalk.red(`\nError: ${error}\n`));
120+
process.exit(1);
121+
}
122+
123+
// If this is a new project (no .tianjirc), initialize project structure
124+
if (!hasTianjirc) {
125+
const initSpinner = ora('Initializing project structure...').start();
126+
try {
127+
const templateDir = path.resolve(
128+
__dirname,
129+
'../../../templates/worker'
130+
);
131+
132+
// Copy template files
133+
await fs.copy(templateDir, currentDir);
134+
135+
// Update package.json with worker name
136+
const packageJsonPath = path.join(currentDir, 'package.json');
137+
const packageJson = await fs.readJson(packageJsonPath);
138+
packageJson.name = worker.name;
139+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
140+
141+
// Update .tianjirc with worker info
142+
const tianjiRcPath = path.join(currentDir, '.tianjirc');
143+
const tianjiRc = await fs.readJson(tianjiRcPath);
144+
tianjiRc.name = worker.name;
145+
tianjiRc.workerId = worker.id;
146+
await fs.writeJson(tianjiRcPath, tianjiRc, { spaces: 2 });
147+
148+
initSpinner.succeed(chalk.green('Project structure initialized'));
149+
} catch (error) {
150+
initSpinner.fail(chalk.red('Failed to initialize project'));
151+
console.error(chalk.red(`\nError: ${error}\n`));
152+
process.exit(1);
153+
}
154+
} else {
155+
// Update existing .tianjirc if worker ID was provided via command line
156+
if (argv.workerId && projectConfig.workerId !== argv.workerId) {
157+
const updateSpinner = ora('Updating .tianjirc...').start();
158+
try {
159+
projectConfig.workerId = argv.workerId;
160+
projectConfig.name = worker.name;
161+
await saveProjectConfig(projectConfig, currentDir);
162+
updateSpinner.succeed(chalk.green('.tianjirc updated'));
163+
} catch (error) {
164+
updateSpinner.fail(chalk.red('Failed to update .tianjirc'));
165+
console.error(chalk.red(`\nError: ${error}\n`));
166+
process.exit(1);
167+
}
168+
}
169+
}
170+
171+
// Save the pulled code to src/index.ts
172+
const saveSpinner = ora('Saving worker code...').start();
173+
try {
174+
const srcDir = path.join(currentDir, 'src');
175+
await fs.ensureDir(srcDir);
176+
177+
const srcFilePath = path.join(srcDir, 'index.ts');
178+
179+
// Check if file exists and overwrite flag is not set
180+
if (await fs.pathExists(srcFilePath)) {
181+
if (!argv.overwrite) {
182+
saveSpinner.fail(chalk.red('File exists'));
183+
console.error(
184+
chalk.red(
185+
'\nError: src/index.ts already exists. Use --overwrite to overwrite it.\n'
186+
)
187+
);
188+
console.log(
189+
chalk.yellow(
190+
'⚠️ To prevent accidental data loss, you must explicitly use --overwrite flag:\n'
191+
)
192+
);
193+
console.log(chalk.white(' tianji worker pull --overwrite\n'));
194+
process.exit(1);
195+
} else {
196+
saveSpinner.text = 'Overwriting existing worker code...';
197+
}
198+
}
199+
200+
await fs.writeFile(srcFilePath, worker.code, 'utf-8');
201+
202+
saveSpinner.succeed(chalk.green('Worker code saved'));
203+
204+
// Show warning about JavaScript code
205+
console.log(
206+
chalk.yellow(
207+
'\n⚠️ Warning: The pulled code is JavaScript but saved as .ts file.\n'
208+
)
209+
);
210+
} catch (error) {
211+
saveSpinner.fail(chalk.red('Failed to save worker code'));
212+
console.error(chalk.red(`\nError: ${error}\n`));
213+
process.exit(1);
214+
}
215+
216+
// Show success message
217+
console.log(chalk.cyan('\n✨ Worker pulled successfully!\n'));
218+
console.log(chalk.white(` Worker ID: ${worker.id}`));
219+
console.log(chalk.white(` Name: ${worker.name}`));
220+
if (worker.description) {
221+
console.log(chalk.white(` Description: ${worker.description}`));
222+
}
223+
224+
// Show next steps
225+
console.log(chalk.cyan('\n📝 Next steps:\n'));
226+
if (!hasTianjirc) {
227+
console.log(chalk.white(' npm install'));
228+
}
229+
console.log(chalk.white(' # Edit src/index.ts to modify your worker'));
230+
console.log(chalk.white(' npm run build'));
231+
console.log(chalk.white(' tianji worker deploy'));
232+
console.log(chalk.cyan('\n✨ Happy coding!\n'));
233+
} catch (error) {
234+
console.error(chalk.red(`\nError: ${error}\n`));
235+
process.exit(1);
236+
}
237+
};

packages/cli/src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { hideBin } from 'yargs/helpers';
55
import * as loginCommand from './commands/login.js';
66
import * as workerInitCommand from './commands/worker/init.js';
77
import * as workerDeployCommand from './commands/worker/deploy.js';
8+
import * as workerPullCommand from './commands/worker/pull.js';
89

910
yargs(hideBin(process.argv))
1011
.scriptName('tianji')
@@ -14,7 +15,11 @@ yargs(hideBin(process.argv))
1415
return yargs
1516
.command(workerInitCommand)
1617
.command(workerDeployCommand)
17-
.demandCommand(1, 'You need to specify a subcommand (init or deploy)')
18+
.command(workerPullCommand)
19+
.demandCommand(
20+
1,
21+
'You need to specify a subcommand (init, deploy, or pull)'
22+
)
1823
.help();
1924
})
2025
.demandCommand(1, 'You need to specify a command')

0 commit comments

Comments
 (0)