@poetora/cli 0.1.4 → 0.1.6

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @poetora/cli@0.1.3 build /home/runner/work/poetora/poetora/packages/cli
2
+ > @poetora/cli@0.1.5 build /home/runner/work/poetora/poetora/packages/cli
3
3
  > tsc --project tsconfig.build.json
4
4
 
@@ -1,6 +1,8 @@
1
+ import chalk from 'chalk';
1
2
  import yargs from 'yargs';
2
3
  import { hideBin } from 'yargs/helpers';
3
4
  import { CheckCommand, DevCommand, InitCommand, LinkCommand, UpdateCommand, } from './commands/index.js';
5
+ import { checkNodeVersion, suppressConsoleWarnings } from './middlewares.js';
4
6
  import { AccessibilityCheckService, LinkService, OpenApiCheckService, PortService, TemplateService, UpdateService, VersionService, } from './services/index.js';
5
7
  import { ConsoleLogger } from './utils/index.js';
6
8
  export class CliBuilder {
@@ -25,6 +27,9 @@ export class CliBuilder {
25
27
  const updateCommand = new UpdateCommand(this.logger, updateService, this.packageName);
26
28
  return (yargs(hideBin(process.argv))
27
29
  .scriptName(this.packageName)
30
+ .version()
31
+ .middleware(checkNodeVersion)
32
+ .middleware(suppressConsoleWarnings)
28
33
  .command('dev', 'initialize a local preview environment', (yargs) => yargs
29
34
  .option('port', {
30
35
  type: 'number',
@@ -163,8 +168,8 @@ export class CliBuilder {
163
168
  })
164
169
  .command(['version', 'v'], 'display the current version of the CLI and client', () => undefined, async () => {
165
170
  const versions = versionService.getVersions();
166
- this.logger.log(`cli version: ${versions.cli}`);
167
- this.logger.log(`client version: ${versions.client}`);
171
+ console.log(`${chalk.bold.green('cli version')} ${versions.cli}`);
172
+ console.log(`${chalk.bold.green('client version')} ${versions.client}`);
168
173
  process.exit(0);
169
174
  })
170
175
  .strictCommands()
package/bin/cli.js CHANGED
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import { Logs } from '@poetora/previewing';
3
+ import { render } from 'ink';
4
+ import React from 'react';
2
5
  import { CliBuilder } from './cli-builder.js';
3
6
  export const cli = (options) => {
7
+ render(React.createElement(Logs));
4
8
  const builder = new CliBuilder(options.packageName);
5
9
  builder.run().catch((error) => {
6
10
  console.error('Fatal error:', error);
@@ -26,12 +26,12 @@ export class BaseCommand {
26
26
  else if (error instanceof Error) {
27
27
  this.logger.error(error.message);
28
28
  if (process.env.DEBUG === 'true') {
29
- console.error(error.stack);
29
+ this.logger.logColor(error.stack ?? '', 'gray');
30
30
  }
31
31
  }
32
32
  else {
33
33
  this.logger.error('An unexpected error occurred');
34
- console.error(error);
34
+ this.logger.logColor(String(error), 'gray');
35
35
  }
36
36
  }
37
37
  exit(code) {
@@ -1,7 +1,7 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
1
  import { categorizeFilePaths, getPoetIgnore } from '@poetora/prebuild';
4
2
  import { coreRemark } from '@poetora/shared';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
5
  import { visit } from 'unist-util-visit';
6
6
  const checkAltAttributes = (filePath, content) => {
7
7
  const issues = [];
@@ -0,0 +1,2 @@
1
+ export declare const checkNodeVersion: () => Promise<void>;
2
+ export declare const suppressConsoleWarnings: () => void;
@@ -0,0 +1,47 @@
1
+ import { addLog, ErrorLog } from '@poetora/previewing';
2
+ import React from 'react';
3
+ export const checkNodeVersion = async () => {
4
+ let nodeVersionString = process.version;
5
+ if (nodeVersionString.charAt(0) === 'v') {
6
+ nodeVersionString = nodeVersionString.slice(1);
7
+ }
8
+ const versionArr = nodeVersionString.split('.');
9
+ const versionStr = versionArr[0];
10
+ if (!versionStr) {
11
+ addLog(React.createElement(ErrorLog, {
12
+ message: `Unable to determine Node.js version (got "${process.version}"). Please ensure you are running Node.js >= 18.0.0.`,
13
+ }));
14
+ process.exit(1);
15
+ }
16
+ const majorVersion = parseInt(versionStr, 10);
17
+ if (majorVersion < 18) {
18
+ addLog(React.createElement(ErrorLog, {
19
+ message: `poetora requires Node.js >= 18.0.0 (current version ${nodeVersionString}). Please upgrade Node.js and try again.`,
20
+ }));
21
+ process.exit(1);
22
+ }
23
+ };
24
+ export const suppressConsoleWarnings = () => {
25
+ const ignoredMessages = [
26
+ 'No utility classes were detected',
27
+ 'https://tailwindcss.com/docs/content-configuration',
28
+ 'DeprecationWarning',
29
+ 'punycode',
30
+ ];
31
+ const originalConsoleError = console.error;
32
+ console.error = (...args) => {
33
+ const message = args.join(' ');
34
+ if (ignoredMessages.some((ignoredMessage) => message.includes(ignoredMessage))) {
35
+ return;
36
+ }
37
+ originalConsoleError.apply(console, args);
38
+ };
39
+ const originalConsoleWarn = console.warn;
40
+ console.warn = (...args) => {
41
+ const message = args.join(' ');
42
+ if (ignoredMessages.some((ignoredMessage) => message.includes(ignoredMessage))) {
43
+ return;
44
+ }
45
+ originalConsoleWarn.apply(console, args);
46
+ };
47
+ };
@@ -1,6 +1,6 @@
1
- import path from 'path';
2
1
  import { getConfigObj, getConfigPath } from '@poetora/prebuild';
3
2
  import { getBackgroundColors } from '@poetora/shared';
3
+ import path from 'path';
4
4
  import { checkDocsColors, } from '../accessibility.js';
5
5
  import { CMD_EXEC_PATH } from '../constants.js';
6
6
  import { checkMdxAccessibility } from '../mdxAccessibility.js';
@@ -1,5 +1,5 @@
1
- import * as path from 'path';
2
1
  import { getBrokenInternalLinks, renameFilesAndUpdateLinksInContent, } from '@poetora/link-rot';
2
+ import * as path from 'path';
3
3
  export class LinkService {
4
4
  logger;
5
5
  constructor(logger) {
@@ -26,7 +26,7 @@ export class LinkService {
26
26
  this.logger.log(`found ${this.logger.highlight?.(brokenLinks.length.toString()) ?? brokenLinks.length} broken link${brokenLinks.length === 1 ? '' : 's'} in ${this.logger.highlight?.(fileCount.toString()) ?? fileCount} file${fileCount === 1 ? '' : 's'}`);
27
27
  this.logger.logNewLine();
28
28
  for (const [filename, links] of Object.entries(brokenLinksByFile)) {
29
- console.log(`\x1b[4m${filename}\x1b[0m`);
29
+ this.logger.logColor(filename, 'cyan');
30
30
  links.forEach((link) => {
31
31
  this.logger.logColor(` ⎿ ${link}`, 'gray');
32
32
  });
@@ -1,5 +1,5 @@
1
- import * as fs from 'fs';
2
1
  import { getOpenApiDocumentFromUrl, isAllowedLocalSchemaUrl, validate } from '@poetora/shared';
2
+ import * as fs from 'fs';
3
3
  import * as yaml from 'js-yaml';
4
4
  import { FileSystemError, ValidationError } from '../errors/index.js';
5
5
  export class OpenApiCheckService {
@@ -1,8 +1,8 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
1
  import { docsConfigSchema } from '@poetora/validation';
4
2
  import AdmZip from 'adm-zip';
3
+ import * as fs from 'fs';
5
4
  import * as fse from 'fs-extra';
5
+ import * as path from 'path';
6
6
  import { ExternalServiceError, FileSystemError } from '../errors/index.js';
7
7
  export class TemplateService {
8
8
  TEMPLATE_URL = 'https://github.com/poetora/starter/archive/refs/heads/main.zip';
@@ -1,5 +1,5 @@
1
- import { execSync } from 'child_process';
2
1
  import { getClientVersion, LOCAL_LINKED_CLI_VERSION } from '@poetora/previewing';
2
+ import { execSync } from 'child_process';
3
3
  import yargs from 'yargs';
4
4
  import { InvalidEnvironmentError } from '../errors/index.js';
5
5
  import { CLI_CONSTANTS } from '../types/index.js';
@@ -55,15 +55,15 @@ export class VersionService {
55
55
  };
56
56
  }
57
57
  getCliVersion() {
58
- if (process.env.CLI_TEST_MODE === 'true') {
59
- return 'test-cli';
60
- }
61
58
  const y = yargs();
62
59
  let version;
63
60
  y.showVersion((v) => {
64
61
  version = v;
65
62
  return false;
66
63
  });
64
+ if (process.env.CLI_TEST_MODE === 'true') {
65
+ return 'test-cli';
66
+ }
67
67
  if (version === 'unknown') {
68
68
  version = LOCAL_LINKED_CLI_VERSION;
69
69
  }
@@ -1,65 +1,61 @@
1
+ import { addLog, EmptyLineLog, ErrorLog, InfoLog, SpinnerLog, SuccessLog, WarningLog, } from '@poetora/previewing';
1
2
  import chalk from 'chalk';
2
- import ora from 'ora';
3
+ import { Text } from 'ink';
4
+ import React from 'react';
3
5
  export class ConsoleLogger {
4
6
  info(message) {
5
- console.log(chalk.blue(`ℹ ${message}`));
7
+ addLog(React.createElement(InfoLog, { message }));
6
8
  }
7
9
  success(message) {
8
- console.log(chalk.green(`✓ ${message}`));
10
+ addLog(React.createElement(SuccessLog, { message }));
9
11
  }
10
12
  error(message) {
11
- console.error(chalk.red(`✗ ${message}`));
13
+ addLog(React.createElement(ErrorLog, { message }));
12
14
  }
13
15
  warn(message) {
14
- console.warn(chalk.yellow(`⚠ ${message}`));
16
+ addLog(React.createElement(WarningLog, { message }));
15
17
  }
16
18
  spinner(message) {
17
- return new ConsoleSpinner(message);
19
+ addLog(React.createElement(SpinnerLog, { message }));
20
+ return new InkSpinner();
18
21
  }
19
22
  log(message) {
20
- console.log(message);
23
+ addLog(React.createElement(Text, {}, message));
21
24
  }
22
25
  logColor(message, color) {
23
- console.log(chalk[color](message));
26
+ addLog(React.createElement(Text, { color }, message));
24
27
  }
25
28
  logBold(message, color) {
26
- if (color) {
27
- console.log(chalk[color].bold(message));
28
- }
29
- else {
30
- console.log(chalk.bold(message));
31
- }
29
+ addLog(React.createElement(Text, { bold: true, color }, message));
32
30
  }
33
31
  logSeparator() {
34
- console.log(chalk.gray('─'.repeat(50)));
32
+ addLog(React.createElement(Text, { color: 'gray' }, '─'.repeat(50)));
35
33
  }
36
34
  logNewLine() {
37
- console.log();
35
+ addLog(React.createElement(EmptyLineLog));
38
36
  }
39
37
  logHeader(message) {
40
- console.log();
41
- console.log(chalk.bold.cyan(message));
42
- console.log();
38
+ this.logNewLine();
39
+ addLog(React.createElement(Text, { bold: true, color: 'cyan' }, message));
40
+ this.logNewLine();
43
41
  }
44
42
  highlight(text) {
45
43
  return chalk.yellow(text);
46
44
  }
47
45
  }
48
- class ConsoleSpinner {
49
- spinner;
50
- constructor(message) {
51
- this.spinner = ora(message);
52
- }
46
+ class InkSpinner {
53
47
  start() {
54
- this.spinner.start();
55
48
  }
56
49
  stop() {
57
- this.spinner.stop();
58
50
  }
59
51
  succeed(message) {
60
- this.spinner.succeed(message);
52
+ if (message) {
53
+ addLog(React.createElement(SuccessLog, { message }));
54
+ }
61
55
  }
62
56
  fail(message) {
63
- this.spinner.fail(message);
57
+ if (message) {
58
+ addLog(React.createElement(ErrorLog, { message }));
59
+ }
64
60
  }
65
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poetora/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "The CLI for Poetora documentation Engine",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -32,20 +32,20 @@
32
32
  "color": "4.2.3",
33
33
  "detect-port": "1.5.1",
34
34
  "front-matter": "4.0.2",
35
- "fs-extra": "11.2.0",
35
+ "fs-extra": "11.1.0",
36
+ "ink": "6.3.0",
36
37
  "inquirer": "12.3.0",
37
38
  "js-yaml": "4.1.0",
38
39
  "mdast-util-mdx-jsx": "3.2.0",
39
- "ora": "^8.1.1",
40
- "react": "^19.0.1",
40
+ "react": "19.2.3",
41
41
  "semver": "7.7.2",
42
42
  "unist-util-visit": "5.0.0",
43
43
  "yargs": "17.7.1",
44
- "@poetora/link-rot": "0.0.6",
45
- "@poetora/prebuild": "0.1.5",
46
- "@poetora/previewing": "0.1.5",
47
- "@poetora/shared": "0.1.5",
48
- "@poetora/validation": "0.1.5"
44
+ "@poetora/link-rot": "0.0.7",
45
+ "@poetora/prebuild": "0.1.6",
46
+ "@poetora/previewing": "0.1.7",
47
+ "@poetora/shared": "0.1.6",
48
+ "@poetora/validation": "0.1.6"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@tsconfig/recommended": "1.0.2",
@@ -53,9 +53,10 @@
53
53
  "@types/color": "^3.0.3",
54
54
  "@types/detect-port": "1.3.2",
55
55
  "@types/fs-extra": "^9.0.13",
56
- "@types/js-yaml": "^4.0.9",
56
+ "@types/js-yaml": "4.0.9",
57
57
  "@types/mdast": "4.0.4",
58
58
  "@types/node": "22.19.2",
59
+ "@types/react": "19.2.7",
59
60
  "@types/yargs": "17.0.22",
60
61
  "@typescript-eslint/eslint-plugin": "8.49.0",
61
62
  "@typescript-eslint/parser": "8.49.0",
@@ -1,7 +1,7 @@
1
+ import chalk from 'chalk';
1
2
  import type { Argv } from 'yargs';
2
3
  import yargs from 'yargs';
3
4
  import { hideBin } from 'yargs/helpers';
4
-
5
5
  import {
6
6
  CheckCommand,
7
7
  DevCommand,
@@ -9,6 +9,7 @@ import {
9
9
  LinkCommand,
10
10
  UpdateCommand,
11
11
  } from './commands/index.js';
12
+ import { checkNodeVersion, suppressConsoleWarnings } from './middlewares.js';
12
13
  import {
13
14
  AccessibilityCheckService,
14
15
  LinkService,
@@ -60,6 +61,9 @@ export class CliBuilder {
60
61
  return (
61
62
  yargs(hideBin(process.argv))
62
63
  .scriptName(this.packageName)
64
+ .version()
65
+ .middleware(checkNodeVersion) // Check Node.js version before any command
66
+ .middleware(suppressConsoleWarnings) // Suppress known console warnings
63
67
  // Dev command
64
68
  .command(
65
69
  'dev',
@@ -245,14 +249,17 @@ export class CliBuilder {
245
249
  () => undefined,
246
250
  async () => {
247
251
  const versions = versionService.getVersions();
248
- this.logger.log(`cli version: ${versions.cli}`);
249
- this.logger.log(`client version: ${versions.client}`);
252
+ // Use chalk for consistent formatting
253
+ console.log(`${chalk.bold.green('cli version')} ${versions.cli}`);
254
+ console.log(`${chalk.bold.green('client version')} ${versions.client}`);
250
255
  process.exit(0);
251
256
  }
252
257
  )
253
258
  // Error handling
254
259
  .strictCommands()
255
260
  .demandCommand(1, 'unknown command. see above for the list of supported commands.')
261
+
262
+ // Alias option flags --help = -h, default --version = -v
256
263
  .alias('h', 'help')
257
264
  .alias('v', 'version')
258
265
  );
package/src/cli.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { Logs } from '@poetora/previewing';
3
+ import { render } from 'ink';
4
+ import React from 'react';
5
+
2
6
  import { CliBuilder } from './cli-builder.js';
3
7
 
4
8
  export interface CliOptions {
@@ -6,6 +10,9 @@ export interface CliOptions {
6
10
  }
7
11
 
8
12
  export const cli = (options: CliOptions): void => {
13
+ // Initialize ink rendering for logs
14
+ render(React.createElement(Logs));
15
+
9
16
  const builder = new CliBuilder(options.packageName);
10
17
 
11
18
  builder.run().catch((error) => {
@@ -104,14 +104,11 @@ describe('BaseCommand', () => {
104
104
  (command as unknown as { execute: typeof command.execute }).execute = vi
105
105
  .fn()
106
106
  .mockRejectedValue('string error');
107
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
108
107
 
109
108
  await expect(command.run({ value: 'test' })).rejects.toBe('string error');
110
109
 
111
110
  expect(mockLogger.error).toHaveBeenCalledWith('An unexpected error occurred');
112
- expect(consoleErrorSpy).toHaveBeenCalledWith('string error');
113
-
114
- consoleErrorSpy.mockRestore();
111
+ expect(mockLogger.logColor).toHaveBeenCalledWith('string error', 'gray');
115
112
  });
116
113
 
117
114
  it('should show stack trace in debug mode', async () => {
@@ -122,13 +119,10 @@ describe('BaseCommand', () => {
122
119
  (command as unknown as { execute: typeof command.execute }).execute = vi
123
120
  .fn()
124
121
  .mockRejectedValue(error);
125
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
126
122
 
127
123
  await expect(command.run({ value: 'test' })).rejects.toThrow(error);
128
124
 
129
- expect(consoleErrorSpy).toHaveBeenCalledWith(error.stack);
130
-
131
- consoleErrorSpy.mockRestore();
125
+ expect(mockLogger.logColor).toHaveBeenCalledWith(error.stack ?? '', 'gray');
132
126
  process.env.DEBUG = originalDebug;
133
127
  });
134
128
  });
@@ -78,12 +78,12 @@ export abstract class BaseCommand<TOptions = unknown, TResult = void> {
78
78
 
79
79
  // In debug mode, show stack trace
80
80
  if (process.env.DEBUG === 'true') {
81
- console.error(error.stack);
81
+ this.logger.logColor(error.stack ?? '', 'gray');
82
82
  }
83
83
  } else {
84
84
  // Unknown error types
85
85
  this.logger.error('An unexpected error occurred');
86
- console.error(error);
86
+ this.logger.logColor(String(error), 'gray');
87
87
  }
88
88
  }
89
89
 
@@ -1,9 +1,9 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
1
  import { categorizeFilePaths, getPoetIgnore } from '@poetora/prebuild';
4
2
  import { coreRemark } from '@poetora/shared';
3
+ import fs from 'fs';
5
4
  import type { Node, Root, Text } from 'mdast';
6
5
  import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
6
+ import path from 'path';
7
7
  import { visit } from 'unist-util-visit';
8
8
 
9
9
  export interface AccessibilityFixAttribute {
@@ -0,0 +1,73 @@
1
+ import { addLog, ErrorLog } from '@poetora/previewing';
2
+ import React from 'react';
3
+
4
+ /**
5
+ * Yargs middleware functions
6
+ * These run before command execution
7
+ */
8
+
9
+ /**
10
+ * Check if Node.js version is supported
11
+ * Middleware that validates Node version before any command runs
12
+ *
13
+ * Minimum requirement: Node.js >= 18.0.0
14
+ * Note: Individual commands may have stricter requirements (e.g., dev command)
15
+ */
16
+ export const checkNodeVersion = async (): Promise<void> => {
17
+ let nodeVersionString = process.version;
18
+ if (nodeVersionString.charAt(0) === 'v') {
19
+ nodeVersionString = nodeVersionString.slice(1);
20
+ }
21
+ const versionArr = nodeVersionString.split('.');
22
+ const versionStr = versionArr[0];
23
+ if (!versionStr) {
24
+ addLog(
25
+ React.createElement(ErrorLog, {
26
+ message: `Unable to determine Node.js version (got "${process.version}"). Please ensure you are running Node.js >= 18.0.0.`,
27
+ })
28
+ );
29
+ process.exit(1);
30
+ }
31
+ const majorVersion = parseInt(versionStr, 10);
32
+
33
+ if (majorVersion < 18) {
34
+ addLog(
35
+ React.createElement(ErrorLog, {
36
+ message: `poetora requires Node.js >= 18.0.0 (current version ${nodeVersionString}). Please upgrade Node.js and try again.`,
37
+ })
38
+ );
39
+ process.exit(1);
40
+ }
41
+ };
42
+
43
+ /**
44
+ * Suppress common console warnings that don't affect functionality
45
+ * Filters out known noise from dependencies
46
+ */
47
+ export const suppressConsoleWarnings = (): void => {
48
+ // Ignore tailwind warnings and punycode deprecation warning
49
+ const ignoredMessages = [
50
+ 'No utility classes were detected',
51
+ 'https://tailwindcss.com/docs/content-configuration',
52
+ 'DeprecationWarning',
53
+ 'punycode',
54
+ ];
55
+
56
+ const originalConsoleError = console.error;
57
+ console.error = (...args) => {
58
+ const message = args.join(' ');
59
+ if (ignoredMessages.some((ignoredMessage) => message.includes(ignoredMessage))) {
60
+ return;
61
+ }
62
+ originalConsoleError.apply(console, args);
63
+ };
64
+
65
+ const originalConsoleWarn = console.warn;
66
+ console.warn = (...args) => {
67
+ const message = args.join(' ');
68
+ if (ignoredMessages.some((ignoredMessage) => message.includes(ignoredMessage))) {
69
+ return;
70
+ }
71
+ originalConsoleWarn.apply(console, args);
72
+ };
73
+ };
@@ -1,5 +1,5 @@
1
- import * as fs from 'fs';
2
1
  import AdmZip from 'adm-zip';
2
+ import * as fs from 'fs';
3
3
  import * as fse from 'fs-extra';
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
  import { ExternalServiceError, FileSystemError } from '../../errors/index.js';
@@ -1,6 +1,6 @@
1
- import path from 'path';
2
1
  import { getConfigObj, getConfigPath } from '@poetora/prebuild';
3
2
  import { getBackgroundColors } from '@poetora/shared';
3
+ import path from 'path';
4
4
  import {
5
5
  type AccessibilityCheckResult,
6
6
  type ContrastResult,
@@ -1,9 +1,9 @@
1
- import * as path from 'path';
2
1
  import {
3
2
  getBrokenInternalLinks,
4
3
  type MdxPath,
5
4
  renameFilesAndUpdateLinksInContent,
6
5
  } from '@poetora/link-rot';
6
+ import * as path from 'path';
7
7
 
8
8
  import type { ILogger } from '../utils/index.js';
9
9
 
@@ -46,7 +46,7 @@ export class LinkService {
46
46
 
47
47
  for (const [filename, links] of Object.entries(brokenLinksByFile)) {
48
48
  // Underline filename for better visibility
49
- console.log(`\x1b[4m${filename}\x1b[0m`);
49
+ this.logger.logColor(filename, 'cyan');
50
50
  links.forEach((link) => {
51
51
  this.logger.logColor(` ⎿ ${link}`, 'gray');
52
52
  });
@@ -1,5 +1,5 @@
1
- import * as fs from 'fs';
2
1
  import { getOpenApiDocumentFromUrl, isAllowedLocalSchemaUrl, validate } from '@poetora/shared';
2
+ import * as fs from 'fs';
3
3
  import * as yaml from 'js-yaml';
4
4
 
5
5
  import { FileSystemError, ValidationError } from '../errors/index.js';
@@ -1,8 +1,8 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
1
  import { docsConfigSchema } from '@poetora/validation';
4
2
  import AdmZip from 'adm-zip';
3
+ import * as fs from 'fs';
5
4
  import * as fse from 'fs-extra';
5
+ import * as path from 'path';
6
6
 
7
7
  import { ExternalServiceError, FileSystemError } from '../errors/index.js';
8
8
 
@@ -1,5 +1,5 @@
1
- import { execSync } from 'child_process';
2
1
  import { getClientVersion, LOCAL_LINKED_CLI_VERSION } from '@poetora/previewing';
2
+ import { execSync } from 'child_process';
3
3
  import yargs from 'yargs';
4
4
 
5
5
  import { InvalidEnvironmentError } from '../errors/index.js';
@@ -83,10 +83,6 @@ export class VersionService {
83
83
  * Get CLI version from package.json
84
84
  */
85
85
  getCliVersion(): string | undefined {
86
- if (process.env.CLI_TEST_MODE === 'true') {
87
- return 'test-cli';
88
- }
89
-
90
86
  const y = yargs();
91
87
  let version: string | undefined;
92
88
 
@@ -95,7 +91,11 @@ export class VersionService {
95
91
  return false;
96
92
  });
97
93
 
98
- // Handle npm link case where version is 'unknown'
94
+ if (process.env.CLI_TEST_MODE === 'true') {
95
+ return 'test-cli';
96
+ }
97
+
98
+ // When running npm link or pnpm link, the version is 'unknown'
99
99
  if (version === 'unknown') {
100
100
  version = LOCAL_LINKED_CLI_VERSION;
101
101
  }
@@ -1,37 +1,49 @@
1
+ import {
2
+ addLog,
3
+ EmptyLineLog,
4
+ ErrorLog,
5
+ InfoLog,
6
+ SpinnerLog,
7
+ SuccessLog,
8
+ WarningLog,
9
+ } from '@poetora/previewing';
1
10
  import chalk from 'chalk';
2
- import ora, { type Ora } from 'ora';
11
+ import { Text } from 'ink';
12
+ import React from 'react';
3
13
 
4
14
  import type { LoggerSpinner } from '../types/index.js';
5
15
 
6
16
  import type { ILogger } from './logger.interface.js';
7
17
 
8
18
  /**
9
- * Console-based logger implementation using chalk for colors
10
- * Provides rich colored output similar to ink but with simpler console-based approach
19
+ * Logger implementation using ink and addLog from @poetora/previewing
20
+ * Provides consistent output with the preview server's logging system
11
21
  */
12
22
  export class ConsoleLogger implements ILogger {
13
23
  info(message: string): void {
14
- console.log(chalk.blue(`ℹ ${message}`));
24
+ addLog(React.createElement(InfoLog, { message }));
15
25
  }
16
26
 
17
27
  success(message: string): void {
18
- console.log(chalk.green(`✓ ${message}`));
28
+ addLog(React.createElement(SuccessLog, { message }));
19
29
  }
20
30
 
21
31
  error(message: string): void {
22
- console.error(chalk.red(`✗ ${message}`));
32
+ addLog(React.createElement(ErrorLog, { message }));
23
33
  }
24
34
 
25
35
  warn(message: string): void {
26
- console.warn(chalk.yellow(`⚠ ${message}`));
36
+ addLog(React.createElement(WarningLog, { message }));
27
37
  }
28
38
 
29
39
  spinner(message: string): LoggerSpinner {
30
- return new ConsoleSpinner(message);
40
+ // Immediately add spinner log
41
+ addLog(React.createElement(SpinnerLog, { message }));
42
+ return new InkSpinner();
31
43
  }
32
44
 
33
45
  log(message: string): void {
34
- console.log(message);
46
+ addLog(React.createElement(Text, {}, message));
35
47
  }
36
48
 
37
49
  /**
@@ -41,45 +53,42 @@ export class ConsoleLogger implements ILogger {
41
53
  message: string,
42
54
  color: 'green' | 'red' | 'yellow' | 'blue' | 'cyan' | 'magenta' | 'gray'
43
55
  ): void {
44
- console.log(chalk[color](message));
56
+ addLog(React.createElement(Text, { color }, message));
45
57
  }
46
58
 
47
59
  /**
48
60
  * Log bold text
49
61
  */
50
62
  logBold(message: string, color?: 'green' | 'red' | 'yellow' | 'blue' | 'cyan' | 'magenta'): void {
51
- if (color) {
52
- console.log(chalk[color].bold(message));
53
- } else {
54
- console.log(chalk.bold(message));
55
- }
63
+ addLog(React.createElement(Text, { bold: true, color }, message));
56
64
  }
57
65
 
58
66
  /**
59
67
  * Log a separator line
60
68
  */
61
69
  logSeparator(): void {
62
- console.log(chalk.gray('─'.repeat(50)));
70
+ addLog(React.createElement(Text, { color: 'gray' }, '─'.repeat(50)));
63
71
  }
64
72
 
65
73
  /**
66
74
  * Log an empty line
67
75
  */
68
76
  logNewLine(): void {
69
- console.log();
77
+ addLog(React.createElement(EmptyLineLog));
70
78
  }
71
79
 
72
80
  /**
73
81
  * Log a section header
74
82
  */
75
83
  logHeader(message: string): void {
76
- console.log();
77
- console.log(chalk.bold.cyan(message));
78
- console.log();
84
+ this.logNewLine();
85
+ addLog(React.createElement(Text, { bold: true, color: 'cyan' }, message));
86
+ this.logNewLine();
79
87
  }
80
88
 
81
89
  /**
82
90
  * Highlight text (typically numbers) with yellow color
91
+ * Note: Returns styled text string for embedding in messages
83
92
  */
84
93
  highlight(text: string): string {
85
94
  return chalk.yellow(text);
@@ -87,28 +96,28 @@ export class ConsoleLogger implements ILogger {
87
96
  }
88
97
 
89
98
  /**
90
- * Console spinner implementation using ora
99
+ * Spinner implementation for ink-based logging
100
+ * Note: In ink/React rendering model, spinners are added as logs
101
+ * and don't need explicit start/stop/succeed/fail methods
91
102
  */
92
- class ConsoleSpinner implements LoggerSpinner {
93
- private spinner: Ora;
94
-
95
- constructor(message: string) {
96
- this.spinner = ora(message);
97
- }
98
-
103
+ class InkSpinner implements LoggerSpinner {
99
104
  start(): void {
100
- this.spinner.start();
105
+ // No-op: spinner already added to log stream
101
106
  }
102
107
 
103
108
  stop(): void {
104
- this.spinner.stop();
109
+ // No-op: will be replaced by next log
105
110
  }
106
111
 
107
112
  succeed(message?: string): void {
108
- this.spinner.succeed(message);
113
+ if (message) {
114
+ addLog(React.createElement(SuccessLog, { message }));
115
+ }
109
116
  }
110
117
 
111
118
  fail(message?: string): void {
112
- this.spinner.fail(message);
119
+ if (message) {
120
+ addLog(React.createElement(ErrorLog, { message }));
121
+ }
113
122
  }
114
123
  }