@telemetryos/cli 1.6.0 → 1.6.1
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/CHANGELOG.md +12 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +67 -0
- package/dist/commands/init.js +7 -9
- package/dist/commands/root.d.ts +2 -0
- package/dist/commands/root.js +5 -0
- package/dist/commands/serve.d.ts +2 -0
- package/dist/commands/serve.js +7 -0
- package/dist/index.js +2 -0
- package/dist/services/config.d.ts +5 -0
- package/dist/services/config.js +23 -0
- package/dist/services/generate-application.d.ts +10 -0
- package/dist/services/generate-application.js +82 -0
- package/dist/services/run-server.d.ts +5 -0
- package/dist/services/run-server.js +226 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @telemetryos/cli
|
|
2
2
|
|
|
3
|
+
## 1.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add scripts for release process
|
|
8
|
+
|
|
9
|
+
Adds a few scripts to help make the release process for these packages
|
|
10
|
+
smoother. Fixes a few issues with undeployed changes from the last release.
|
|
11
|
+
|
|
12
|
+
- Updated dependencies
|
|
13
|
+
- @telemetryos/development-application-host-ui@1.6.1
|
|
14
|
+
|
|
3
15
|
## 1.6.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { loadConfig, saveConfig } from '../services/config.js';
|
|
4
|
+
export const authCommand = new Command('auth')
|
|
5
|
+
.description('Authenticate with TelemetryOS by saving your API token')
|
|
6
|
+
.option('-t, --token <string>', 'API token (skip interactive prompt)')
|
|
7
|
+
.option('-f, --force', 'Replace existing token without confirmation')
|
|
8
|
+
.action(handleAuthCommand);
|
|
9
|
+
function maskToken(token) {
|
|
10
|
+
if (token.length <= 8)
|
|
11
|
+
return '****';
|
|
12
|
+
return `${token.slice(0, 4)}...${token.slice(-4)}`;
|
|
13
|
+
}
|
|
14
|
+
function validateToken(input) {
|
|
15
|
+
if (!input || input.trim().length === 0) {
|
|
16
|
+
return 'Token cannot be empty';
|
|
17
|
+
}
|
|
18
|
+
const trimmed = input.trim();
|
|
19
|
+
if (!/^[a-f0-9-]+$/i.test(trimmed)) {
|
|
20
|
+
return 'Token must be a valid string';
|
|
21
|
+
}
|
|
22
|
+
if (trimmed.length < 24) {
|
|
23
|
+
return 'Token must be at least 24 characters';
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
async function handleAuthCommand(options) {
|
|
28
|
+
let token = options.token;
|
|
29
|
+
const existingConfig = await loadConfig();
|
|
30
|
+
const existingToken = existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.apiToken;
|
|
31
|
+
if (existingToken && !options.force) {
|
|
32
|
+
const { confirm } = await inquirer.prompt([
|
|
33
|
+
{
|
|
34
|
+
type: 'confirm',
|
|
35
|
+
name: 'confirm',
|
|
36
|
+
message: `A token already exists (${maskToken(existingToken)}). Do you want to replace it?`,
|
|
37
|
+
default: false,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
if (!confirm) {
|
|
41
|
+
console.log('Authentication cancelled.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (token) {
|
|
46
|
+
const validation = validateToken(token);
|
|
47
|
+
if (validation !== true) {
|
|
48
|
+
console.error(validation);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
token = token.trim();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log('In Studio, go to Settings > API Tokens to get your API token.\n');
|
|
55
|
+
const answers = await inquirer.prompt([
|
|
56
|
+
{
|
|
57
|
+
type: 'input',
|
|
58
|
+
name: 'token',
|
|
59
|
+
message: 'Paste your API token:',
|
|
60
|
+
validate: validateToken,
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
token = answers.token.trim();
|
|
64
|
+
}
|
|
65
|
+
await saveConfig({ ...existingConfig, apiToken: token });
|
|
66
|
+
console.log('API token saved successfully!');
|
|
67
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { generateApplication } from '../generate-application.js';
|
|
2
|
+
import { generateApplication } from '../services/generate-application.js';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
export const initCommand = new Command('init')
|
|
@@ -22,21 +22,21 @@ async function handleInitCommand(projectName, options) {
|
|
|
22
22
|
type: 'input',
|
|
23
23
|
name: 'name',
|
|
24
24
|
message: 'What is the name of your application?',
|
|
25
|
-
validate: (input) => input.length > 0 || 'Application name cannot be empty'
|
|
25
|
+
validate: (input) => input.length > 0 || 'Application name cannot be empty',
|
|
26
26
|
});
|
|
27
27
|
if (!description)
|
|
28
28
|
questions.push({
|
|
29
29
|
type: 'input',
|
|
30
30
|
name: 'description',
|
|
31
31
|
message: 'What is the description of your application?',
|
|
32
|
-
default: 'A telemetryOS application'
|
|
32
|
+
default: 'A telemetryOS application',
|
|
33
33
|
});
|
|
34
34
|
if (!author)
|
|
35
35
|
questions.push({
|
|
36
36
|
type: 'input',
|
|
37
37
|
name: 'author',
|
|
38
38
|
message: 'Who is the author of your application?',
|
|
39
|
-
default: ''
|
|
39
|
+
default: '',
|
|
40
40
|
});
|
|
41
41
|
if (!version)
|
|
42
42
|
questions.push({
|
|
@@ -44,16 +44,14 @@ async function handleInitCommand(projectName, options) {
|
|
|
44
44
|
name: 'version',
|
|
45
45
|
message: 'What is the version of your application?',
|
|
46
46
|
default: '0.1.0',
|
|
47
|
-
validate: (input) => /^\d+\.\d+\.\d+(-.+)?$/.test(input) || 'Version must be in semver format (e.g. 1.0.0)'
|
|
47
|
+
validate: (input) => /^\d+\.\d+\.\d+(-.+)?$/.test(input) || 'Version must be in semver format (e.g. 1.0.0)',
|
|
48
48
|
});
|
|
49
49
|
if (!template)
|
|
50
50
|
questions.push({
|
|
51
51
|
type: 'list',
|
|
52
52
|
name: 'template',
|
|
53
53
|
message: 'Which template would you like to use?',
|
|
54
|
-
choices: [
|
|
55
|
-
{ name: 'Vite + React + TypeScript', value: 'vite-react-typescript' }
|
|
56
|
-
]
|
|
54
|
+
choices: [{ name: 'Vite + React + TypeScript', value: 'vite-react-typescript' }],
|
|
57
55
|
});
|
|
58
56
|
if (questions.length !== 0) {
|
|
59
57
|
const answers = await inquirer.prompt(questions);
|
|
@@ -78,6 +76,6 @@ async function handleInitCommand(projectName, options) {
|
|
|
78
76
|
projectPath,
|
|
79
77
|
progressFn: (createdFilePath) => {
|
|
80
78
|
console.log(`.${path.sep}${path.relative(process.cwd(), createdFilePath)}`);
|
|
81
|
-
}
|
|
79
|
+
},
|
|
82
80
|
});
|
|
83
81
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { runServer } from '../services/run-server.js';
|
|
3
|
+
export const serveCommand = new Command('serve')
|
|
4
|
+
.description('Serves a telemetryOS application locally for development')
|
|
5
|
+
.option('-p, --port <number>', 'Port to run the development ui on', '6969')
|
|
6
|
+
.argument('[project-path]', 'Path to the telemetry application project. Defaults to current working directory', process.cwd())
|
|
7
|
+
.action(runServer);
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { authCommand } from './commands/auth.js';
|
|
2
3
|
import { initCommand } from './commands/init.js';
|
|
3
4
|
import { rootCommand } from './commands/root.js';
|
|
4
5
|
import { serveCommand } from './commands/serve.js';
|
|
6
|
+
rootCommand.addCommand(authCommand);
|
|
5
7
|
rootCommand.addCommand(serveCommand);
|
|
6
8
|
rootCommand.addCommand(initCommand);
|
|
7
9
|
rootCommand.parse(process.argv);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
function getConfigDir() {
|
|
5
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(homedir(), '.config');
|
|
6
|
+
return path.join(xdgConfig, 'telemetryos-cli');
|
|
7
|
+
}
|
|
8
|
+
function getConfigPath() {
|
|
9
|
+
return path.join(getConfigDir(), 'config.json');
|
|
10
|
+
}
|
|
11
|
+
export async function loadConfig() {
|
|
12
|
+
try {
|
|
13
|
+
const content = await readFile(getConfigPath(), 'utf-8');
|
|
14
|
+
return JSON.parse(content);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function saveConfig(config) {
|
|
21
|
+
await mkdir(getConfigDir(), { recursive: true });
|
|
22
|
+
await writeFile(getConfigPath(), JSON.stringify(config, null, 2));
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type GenerateApplicationOptions = {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
author: string;
|
|
5
|
+
version: string;
|
|
6
|
+
template: string;
|
|
7
|
+
projectPath: string;
|
|
8
|
+
progressFn: (createdFilePath: string) => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function generateApplication(options: GenerateApplicationOptions): Promise<void>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
const ignoredTemplateFiles = ['.DS_Store', 'thumbs.db', 'node_modules', '.git', 'dist'];
|
|
5
|
+
const templatesDir = path.join(import.meta.dirname, '../../templates');
|
|
6
|
+
const dotfileNames = ['gitignore', 'npmrc', 'npmignore', 'env.example'];
|
|
7
|
+
export async function generateApplication(options) {
|
|
8
|
+
const { name, description, author, version, template, projectPath, progressFn } = options;
|
|
9
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
10
|
+
await copyDir(path.join(templatesDir, template), projectPath, {
|
|
11
|
+
name,
|
|
12
|
+
version,
|
|
13
|
+
description,
|
|
14
|
+
author,
|
|
15
|
+
}, progressFn);
|
|
16
|
+
// Install latest versions of @telemetryos/sdk and @telemetryos/cli
|
|
17
|
+
console.log('\nInstalling dependencies...');
|
|
18
|
+
await installPackages(projectPath);
|
|
19
|
+
}
|
|
20
|
+
async function installPackages(projectPath) {
|
|
21
|
+
// Install SDK as a regular dependency
|
|
22
|
+
await new Promise((resolve, reject) => {
|
|
23
|
+
const sdkInstall = spawn('npm', ['install', '@telemetryos/sdk'], {
|
|
24
|
+
cwd: projectPath,
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
});
|
|
27
|
+
sdkInstall.on('close', (code) => {
|
|
28
|
+
if (code === 0) {
|
|
29
|
+
resolve();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
reject(new Error(`npm install @telemetryos/sdk failed with code ${code}`));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
sdkInstall.on('error', (error) => {
|
|
36
|
+
reject(error);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
// Install CLI as a dev dependency
|
|
40
|
+
await new Promise((resolve, reject) => {
|
|
41
|
+
const cliInstall = spawn('npm', ['install', '-D', '@telemetryos/cli'], {
|
|
42
|
+
cwd: projectPath,
|
|
43
|
+
stdio: 'inherit',
|
|
44
|
+
});
|
|
45
|
+
cliInstall.on('close', (code) => {
|
|
46
|
+
if (code === 0) {
|
|
47
|
+
resolve();
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
reject(new Error(`npm install -D @telemetryos/cli failed with code ${code}`));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
cliInstall.on('error', (error) => {
|
|
54
|
+
reject(error);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function copyDir(source, destination, replacements, progressFn) {
|
|
59
|
+
const dirListing = await fs.readdir(source);
|
|
60
|
+
for (const dirEntry of dirListing) {
|
|
61
|
+
if (ignoredTemplateFiles.includes(dirEntry))
|
|
62
|
+
continue;
|
|
63
|
+
const sourcePath = path.join(source, dirEntry);
|
|
64
|
+
const destinationPath = path.join(destination, dotfileNames.includes(dirEntry) ? `.${dirEntry}` : dirEntry);
|
|
65
|
+
const stats = await fs.stat(sourcePath);
|
|
66
|
+
if (stats.isDirectory()) {
|
|
67
|
+
await fs.mkdir(destinationPath, { recursive: true });
|
|
68
|
+
await copyDir(sourcePath, destinationPath, replacements, progressFn);
|
|
69
|
+
}
|
|
70
|
+
else if (stats.isFile()) {
|
|
71
|
+
await copyFile(sourcePath, destinationPath, replacements, progressFn);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function copyFile(source, destination, replacements, progressFn) {
|
|
76
|
+
let contents = await fs.readFile(source, 'utf-8');
|
|
77
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
78
|
+
contents = contents.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
|
79
|
+
}
|
|
80
|
+
await fs.writeFile(destination, contents, 'utf-8');
|
|
81
|
+
progressFn(destination);
|
|
82
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import readable from 'readline/promises';
|
|
6
|
+
import serveHandler from 'serve-handler';
|
|
7
|
+
import z from 'zod';
|
|
8
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
9
|
+
const ansiWhite = '\u001b[37m';
|
|
10
|
+
const ansiYellow = '\u001b[33m';
|
|
11
|
+
const ansiCyan = '\u001b[36m';
|
|
12
|
+
const ansiBold = '\u001b[1m';
|
|
13
|
+
const ansiReset = '\u001b[0m';
|
|
14
|
+
const mountPointSchema = z.object({
|
|
15
|
+
path: z.string(),
|
|
16
|
+
});
|
|
17
|
+
const workerScriptSchema = z.object({
|
|
18
|
+
path: z.string(),
|
|
19
|
+
});
|
|
20
|
+
const containerSchema = z.object({
|
|
21
|
+
image: z.string(),
|
|
22
|
+
});
|
|
23
|
+
const configSchema = z.object({
|
|
24
|
+
name: z.string().optional(),
|
|
25
|
+
version: z.string().optional(),
|
|
26
|
+
thumbnailPath: z.string().optional(),
|
|
27
|
+
mountPoints: z.record(z.string(), z.union([mountPointSchema, z.string()])).optional(),
|
|
28
|
+
workerScripts: z.record(z.string(), z.union([workerScriptSchema, z.string()])).optional(),
|
|
29
|
+
containers: z.record(z.string(), z.union([containerSchema, z.string()])).optional(),
|
|
30
|
+
devServer: z
|
|
31
|
+
.object({
|
|
32
|
+
runCommand: z.string().optional(),
|
|
33
|
+
url: z.string(),
|
|
34
|
+
})
|
|
35
|
+
.optional(),
|
|
36
|
+
});
|
|
37
|
+
export async function runServer(projectPath, flags) {
|
|
38
|
+
printSplashScreen();
|
|
39
|
+
projectPath = path.resolve(process.cwd(), projectPath);
|
|
40
|
+
const telemetryConfig = await loadConfigFile(projectPath);
|
|
41
|
+
if (!telemetryConfig) {
|
|
42
|
+
console.error('No telemetry configuration found. Are you in the right directory?');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const validationResult = configSchema.safeParse(telemetryConfig);
|
|
46
|
+
if (!validationResult.success) {
|
|
47
|
+
console.error('Invalid telemetry.config.json:');
|
|
48
|
+
validationResult.error.issues.forEach((issue) => {
|
|
49
|
+
console.error(` ${issue.path.join('.')}: ${issue.message}`);
|
|
50
|
+
});
|
|
51
|
+
console.error('\n');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
await serveDevelopmentApplicationHostUI(projectPath, flags.port, validationResult.data);
|
|
55
|
+
await serveTelemetryApplication(projectPath, validationResult.data);
|
|
56
|
+
}
|
|
57
|
+
async function serveDevelopmentApplicationHostUI(projectPath, port, telemetryConfig) {
|
|
58
|
+
const hostUiPath = await import.meta.resolve('@telemetryos/development-application-host-ui/dist');
|
|
59
|
+
const serveConfig = { public: hostUiPath.replace('file://', '') };
|
|
60
|
+
const server = http.createServer();
|
|
61
|
+
server.on('request', async (req, res) => {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
const url = new URL(req.url, `http://${req.headers.origin}`);
|
|
64
|
+
if (url.pathname === '/__tos-config__') {
|
|
65
|
+
res.setHeader('Content-Type', 'application/json');
|
|
66
|
+
res.end(JSON.stringify(telemetryConfig));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (url.pathname === '/__tos-thumbnail__') {
|
|
70
|
+
if (!telemetryConfig.thumbnailPath) {
|
|
71
|
+
res.statusCode = 404;
|
|
72
|
+
res.end('No thumbnail configured');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const thumbnailFullPath = path.join(projectPath, telemetryConfig.thumbnailPath);
|
|
76
|
+
try {
|
|
77
|
+
const imageData = await readFile(thumbnailFullPath);
|
|
78
|
+
const ext = path.extname(telemetryConfig.thumbnailPath).toLowerCase();
|
|
79
|
+
const mimeTypes = {
|
|
80
|
+
'.jpg': 'image/jpeg',
|
|
81
|
+
'.jpeg': 'image/jpeg',
|
|
82
|
+
'.png': 'image/png',
|
|
83
|
+
'.gif': 'image/gif',
|
|
84
|
+
'.svg': 'image/svg+xml',
|
|
85
|
+
'.webp': 'image/webp',
|
|
86
|
+
};
|
|
87
|
+
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
88
|
+
res.setHeader('Content-Type', contentType);
|
|
89
|
+
res.end(imageData);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
res.statusCode = 404;
|
|
93
|
+
res.end('Thumbnail file not found');
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (url.pathname === '/__dev_proxy__' && req.method === 'POST' && res) {
|
|
98
|
+
let body = '';
|
|
99
|
+
for await (const chunk of req) {
|
|
100
|
+
body += chunk;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const { url, method, headers, body: requestBody } = JSON.parse(body);
|
|
104
|
+
const response = await fetch(url, {
|
|
105
|
+
method,
|
|
106
|
+
headers,
|
|
107
|
+
body: requestBody !== null && requestBody !== void 0 ? requestBody : undefined,
|
|
108
|
+
});
|
|
109
|
+
const contentType = response.headers.get('content-type') || '';
|
|
110
|
+
const isJson = contentType.includes('application/json');
|
|
111
|
+
const isText = contentType.includes('text/') ||
|
|
112
|
+
contentType.includes('application/javascript') ||
|
|
113
|
+
contentType.includes('application/xml');
|
|
114
|
+
let responseBody;
|
|
115
|
+
let bodyType;
|
|
116
|
+
if (isJson) {
|
|
117
|
+
const text = await response.text();
|
|
118
|
+
responseBody = text ? JSON.parse(text) : null;
|
|
119
|
+
bodyType = 'json';
|
|
120
|
+
}
|
|
121
|
+
else if (isText) {
|
|
122
|
+
responseBody = await response.text();
|
|
123
|
+
bodyType = 'text';
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Binary data - convert to base64 for JSON transport
|
|
127
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
128
|
+
responseBody = Buffer.from(arrayBuffer).toString('base64');
|
|
129
|
+
bodyType = 'binary';
|
|
130
|
+
}
|
|
131
|
+
const responseHeaders = {};
|
|
132
|
+
response.headers.forEach((value, key) => {
|
|
133
|
+
responseHeaders[key] = value;
|
|
134
|
+
});
|
|
135
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
136
|
+
res.end(JSON.stringify({
|
|
137
|
+
success: true,
|
|
138
|
+
status: response.status,
|
|
139
|
+
statusText: response.statusText,
|
|
140
|
+
headers: responseHeaders,
|
|
141
|
+
body: responseBody,
|
|
142
|
+
bodyType,
|
|
143
|
+
ok: response.ok,
|
|
144
|
+
url: response.url,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
149
|
+
res.end(JSON.stringify({
|
|
150
|
+
success: false,
|
|
151
|
+
errorMessage: `Proxy fetch failed: ${String(error)}`,
|
|
152
|
+
errorCause: error.cause ? String((_b = (_a = error.cause) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : error.cause) : undefined,
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
serveHandler(req, res, serveConfig).catch((err) => {
|
|
158
|
+
console.error('Error handling request:', err);
|
|
159
|
+
res.statusCode = 500;
|
|
160
|
+
res.end('Internal Server Error');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
printServerInfo(port);
|
|
164
|
+
server.listen(port);
|
|
165
|
+
}
|
|
166
|
+
async function serveTelemetryApplication(rootPath, telemetryConfig) {
|
|
167
|
+
var _a;
|
|
168
|
+
if (!((_a = telemetryConfig === null || telemetryConfig === void 0 ? void 0 : telemetryConfig.devServer) === null || _a === void 0 ? void 0 : _a.runCommand)) {
|
|
169
|
+
console.log('No value in config at devServer.runCommand');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const runCommand = telemetryConfig.devServer.runCommand;
|
|
173
|
+
const binPath = path.join(rootPath, 'node_modules', '.bin');
|
|
174
|
+
const childProcess = spawn(runCommand, {
|
|
175
|
+
shell: true,
|
|
176
|
+
env: { ...process.env, FORCE_COLOR: '1', PATH: `${binPath}:${process.env.PATH}` },
|
|
177
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
178
|
+
cwd: rootPath,
|
|
179
|
+
});
|
|
180
|
+
const stdoutReadline = readable.createInterface({
|
|
181
|
+
input: childProcess.stdout,
|
|
182
|
+
crlfDelay: Infinity,
|
|
183
|
+
});
|
|
184
|
+
const stderrReadline = readable.createInterface({
|
|
185
|
+
input: childProcess.stderr,
|
|
186
|
+
crlfDelay: Infinity,
|
|
187
|
+
});
|
|
188
|
+
stdoutReadline.on('line', (line) => {
|
|
189
|
+
console.log(`[application]: ${line}`);
|
|
190
|
+
});
|
|
191
|
+
stderrReadline.on('line', (line) => {
|
|
192
|
+
console.error(`[application]: ${line}`);
|
|
193
|
+
});
|
|
194
|
+
process.on('exit', () => {
|
|
195
|
+
childProcess.kill();
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
async function loadConfigFile(rootPath) {
|
|
199
|
+
const configFilePath = path.join(rootPath, 'telemetry.config.json');
|
|
200
|
+
try {
|
|
201
|
+
const fileContent = await readFile(configFilePath, 'utf-8');
|
|
202
|
+
const config = JSON.parse(fileContent);
|
|
203
|
+
return config;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function printSplashScreen() {
|
|
210
|
+
console.log(`
|
|
211
|
+
|
|
212
|
+
${ansiWhite} █ █ █ ${ansiYellow}▄▀▀▀▄ ▄▀▀▀▄
|
|
213
|
+
${ansiWhite} █ █ █ ${ansiYellow}█ █ █
|
|
214
|
+
${ansiWhite}▀█▀ ▄▀▀▄ █ ▄▀▀▄ █▀▄▀▄ ▄▀▀▄ ▀█▀ █▄▀ █ █ ${ansiYellow}█ █ ▀▀▀▄
|
|
215
|
+
${ansiWhite} █ █▀▀▀ █ █▀▀▀ █ █ █ █▀▀▀ █ █ █ █ ${ansiYellow}█ █ █
|
|
216
|
+
${ansiWhite} ▀▄ ▀▄▄▀ █ ▀▄▄▀ █ █ █ ▀▄▄▀ ▀▄ █ █ ${ansiYellow}▀▄▄▄▀ ▀▄▄▄▀
|
|
217
|
+
${ansiWhite} ▄▀ ${ansiReset}
|
|
218
|
+
v${pkg.version}`);
|
|
219
|
+
}
|
|
220
|
+
function printServerInfo(port) {
|
|
221
|
+
console.log(`
|
|
222
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
223
|
+
║ ${ansiBold}Development environment running at: ${ansiCyan}http://localhost:${port}${ansiReset} ║
|
|
224
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
225
|
+
`);
|
|
226
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telemetryos/cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "The official TelemetryOS application CLI package. Use it to build applications that run on the TelemetryOS platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"license": "",
|
|
26
26
|
"repository": "github:TelemetryTV/Application-API",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@telemetryos/development-application-host-ui": "^1.6.
|
|
28
|
+
"@telemetryos/development-application-host-ui": "^1.6.1",
|
|
29
29
|
"@types/serve-handler": "^6.1.4",
|
|
30
30
|
"commander": "^14.0.0",
|
|
31
31
|
"inquirer": "^12.9.6",
|