-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathcli.ts
235 lines (194 loc) · 7.02 KB
/
cli.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { main } from './compilers/node/compile';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import fetch from 'node-fetch';
import { extractVariables } from "./compilers/node/index";
import prompt from 'prompt';
yargs(hideBin(process.argv))
.command('run <file>', 'run the HeroML script', (yargs) => {
return yargs.positional('file', {
describe: 'the HeroML script to run',
type: 'string',
})
.option('output', {
alias: 'o',
describe: 'Output directory',
type: 'string',
default: './outputs',
})
.option('filename', {
alias: 'f',
describe: 'Output file name',
type: 'string',
default: `response_${new Date().toISOString()}.json`,
})
.option('publish', {
alias: 'p',
describe: 'Publish the result to the Cloud Function',
type: 'boolean', // This flag is a boolean, it doesn't need a value
default: false, // By default, do not publish
});
}, runCommand)
.argv;
// File Handling and Validation
async function getFileContents(filePath: string): Promise<string> {
if (!filePath.endsWith('.heroml')) {
throw new Error('File extension must be .heroml');
}
if (!fs.existsSync(filePath)) {
throw new Error('File does not exist');
}
try {
fs.accessSync(filePath, fs.constants.R_OK);
} catch (err) {
throw new Error('File is not readable');
}
return fs.readFileSync(filePath, 'utf8');
}
// Dynamic Variables Handling
async function getDynamicVariables(dynamicInitialValues: any, argv: any) {
let initialValues: { [key: string]: any } = {};
prompt.start();
const get = promisify(prompt.get); // Convert to promise-based function
for(let variable of dynamicInitialValues) {
// Check if the value is already provided via CLI
if(argv[variable]) {
initialValues[variable] = argv[variable];
} else {
// Otherwise, prompt the user for the value
const result = await get([variable]); // Now we can use await here
initialValues[variable] = result[variable];
}
}
return initialValues;
}
// Output Handling
function writeOutput(outputDir: string, filename: string, finalEnvironment: any) {
// Create the output directory if it does not exist
outputDir = path.resolve(outputDir);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Define the output path
const outputPath = path.join(outputDir, filename);
// Write the JSON data to the output file
fs.writeFileSync(outputPath, JSON.stringify(finalEnvironment, null, 2), 'utf8');
console.log(`Success! Output written to ${outputPath}`);
}
// Output Handling for Markdown
function writeMarkdownOutput(outputDir: string, filename: string, finalEnvironment: any) {
// Create the output directory if it does not exist
outputDir = path.resolve(outputDir);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Define the output path
const outputPath = path.join(outputDir, filename + '.md');
// Filter keys that start with 'step_' and sort them
const stepKeys = Object.keys(finalEnvironment)
.filter(key => key.startsWith('step_'))
.sort((a, b) => {
const aStepNumbers = a.split('_').slice(1).map(Number);
const bStepNumbers = b.split('_').slice(1).map(Number);
for (let i = 0; i < Math.min(aStepNumbers.length, bStepNumbers.length); i++) {
if (aStepNumbers[i] !== bStepNumbers[i]) {
return aStepNumbers[i] - bStepNumbers[i];
}
}
return aStepNumbers.length - bStepNumbers.length;
});
// Initialize markdown string
let markdownString = '';
// Add each 'step_' value to the markdown string
for (const key of stepKeys) {
markdownString += finalEnvironment[key] + '\n\n';
}
// Write the markdown string to the output file
fs.writeFileSync(outputPath, markdownString, 'utf8');
console.log(`Success! Markdown output written to ${outputPath}`);
}
// Configuration Handling
function getConfig() {
let config;
try {
const configPath = path.resolve('./heroconfig.json');
const configRaw = fs.readFileSync(configPath, 'utf8');
config = JSON.parse(configRaw);
} catch (err) {
throw new Error('Error reading configuration: ' + err);
}
return config;
}
// Publish Handling
async function publishResults(finalEnvironment: any, config: any) {
console.log("Publishing results...");
const apiKey = config.heroApiToken;
if (!apiKey) {
console.error('Error: Hero API Token missing.\n' +
'You need to add your Hero API Token before you can publish results.\n' +
'Go to https://hero.page -> Click on your profile on the top right of the page ->\n' +
'Account Settings -> Get API Key\n' +
'Then add it to the "heroApiToken" field in your heroconfig.json');
return;
}
const functionUrl = 'https://us-central1-focushero-1650416072840.cloudfunctions.net/api/publishItems';
let response;
try {
response = await fetch(functionUrl, {
method: 'POST',
body: JSON.stringify({data: finalEnvironment}),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
});
} catch (error) {
console.error('Error: Failed to make request to publish results.\n' +
'Please check your internet connection and ensure your API key is valid.\n' +
'Error Details: ', error);
return;
}
if (!response.ok) {
console.error(`Error: Failed to publish results.\n` +
`The server responded with HTTP status: ${response.status}.\n` +
'Please verify your API key and try again.\n' +
'If the problem persists, please contact support.\n' +
'Error Details: ', await response.text());
return;
}
const responseBody = await response.json();
console.log('Published successfully:', responseBody);
}
// Main Execution
async function runCommand(argv: any) {
if (typeof argv.file === 'string') {
try {
const filePath = path.resolve(argv.file);
const heroml = await getFileContents(filePath);
if (!isValidHeroML(heroml)) {
throw new Error('Invalid HeroML script');
}
const dynamicInitialValues = Array.from(new Set(extractVariables(heroml)));
console.log(dynamicInitialValues);
const initialValues = await getDynamicVariables(dynamicInitialValues, argv);
const finalEnvironment = await main(heroml, initialValues);
writeMarkdownOutput(argv.output, argv.filename, finalEnvironment);
writeOutput(argv.output, argv.filename, finalEnvironment);
if (argv.publish) {
const config = getConfig();
await publishResults(finalEnvironment, config);
}
} catch (error) {
console.error('Error:', error);
}
}
}
// HeroML Validation
function isValidHeroML(heroml: string): boolean {
// TODO: implement actual validation
return true;
}