@poetora/cli 0.1.9 → 0.1.11

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.
Files changed (51) hide show
  1. package/bin/cli-builder.js +32 -18
  2. package/bin/services/link.service.js +0 -11
  3. package/bin/services/version.service.d.ts +1 -1
  4. package/bin/services/version.service.js +12 -10
  5. package/bin/utils/index.d.ts +1 -0
  6. package/bin/utils/index.js +1 -0
  7. package/bin/utils/terminate.d.ts +1 -0
  8. package/bin/utils/terminate.js +4 -0
  9. package/package.json +5 -1
  10. package/.turbo/turbo-build.log +0 -4
  11. package/src/accessibility.ts +0 -180
  12. package/src/cli-builder.ts +0 -274
  13. package/src/cli.ts +0 -22
  14. package/src/commands/__tests__/base.command.test.ts +0 -139
  15. package/src/commands/__tests__/dev.command.test.ts +0 -241
  16. package/src/commands/__tests__/init.command.test.ts +0 -281
  17. package/src/commands/__tests__/utils.ts +0 -20
  18. package/src/commands/base.command.ts +0 -97
  19. package/src/commands/check.command.ts +0 -40
  20. package/src/commands/dev.command.ts +0 -63
  21. package/src/commands/index.ts +0 -6
  22. package/src/commands/init.command.ts +0 -125
  23. package/src/commands/link.command.ts +0 -39
  24. package/src/commands/update.command.ts +0 -23
  25. package/src/constants.ts +0 -4
  26. package/src/errors/cli-error.ts +0 -83
  27. package/src/errors/index.ts +0 -1
  28. package/src/index.ts +0 -110
  29. package/src/mdxAccessibility.ts +0 -132
  30. package/src/middlewares.ts +0 -73
  31. package/src/services/__tests__/port.service.test.ts +0 -83
  32. package/src/services/__tests__/template.service.test.ts +0 -234
  33. package/src/services/__tests__/version.service.test.ts +0 -165
  34. package/src/services/accessibility-check.service.ts +0 -226
  35. package/src/services/index.ts +0 -7
  36. package/src/services/link.service.ts +0 -65
  37. package/src/services/openapi-check.service.ts +0 -68
  38. package/src/services/port.service.ts +0 -47
  39. package/src/services/template.service.ts +0 -203
  40. package/src/services/update.service.ts +0 -76
  41. package/src/services/version.service.ts +0 -161
  42. package/src/start.ts +0 -6
  43. package/src/types/common.ts +0 -53
  44. package/src/types/index.ts +0 -2
  45. package/src/types/options.ts +0 -42
  46. package/src/utils/console-logger.ts +0 -123
  47. package/src/utils/index.ts +0 -2
  48. package/src/utils/logger.interface.ts +0 -70
  49. package/tsconfig.build.json +0 -17
  50. package/tsconfig.json +0 -21
  51. package/vitest.config.ts +0 -8
package/src/constants.ts DELETED
@@ -1,4 +0,0 @@
1
- import os from 'os';
2
-
3
- export const HOME_DIR = os.homedir();
4
- export const CMD_EXEC_PATH = process.cwd();
@@ -1,83 +0,0 @@
1
- /**
2
- * Base CLI error class for all custom errors
3
- */
4
- export class CliError extends Error {
5
- constructor(
6
- message: string,
7
- public readonly code: string,
8
- public readonly exitCode: number = 1
9
- ) {
10
- super(message);
11
- this.name = 'CliError';
12
- Error.captureStackTrace(this, this.constructor);
13
- }
14
- }
15
-
16
- /**
17
- * Error thrown when environment requirements are not met
18
- */
19
- export class InvalidEnvironmentError extends CliError {
20
- constructor(message: string) {
21
- super(message, 'INVALID_ENVIRONMENT', 1);
22
- this.name = 'InvalidEnvironmentError';
23
- }
24
- }
25
-
26
- /**
27
- * Error thrown when no available port is found
28
- */
29
- export class NoAvailablePortError extends CliError {
30
- constructor(message: string) {
31
- super(message, 'NO_AVAILABLE_PORT', 1);
32
- this.name = 'NoAvailablePortError';
33
- }
34
- }
35
-
36
- /**
37
- * Error thrown when configuration file is not found
38
- */
39
- export class ConfigNotFoundError extends CliError {
40
- constructor(message: string) {
41
- super(message, 'CONFIG_NOT_FOUND', 1);
42
- this.name = 'ConfigNotFoundError';
43
- }
44
- }
45
-
46
- /**
47
- * Error thrown when command validation fails
48
- */
49
- export class ValidationError extends CliError {
50
- constructor(
51
- message: string,
52
- public readonly field?: string
53
- ) {
54
- super(message, 'VALIDATION_ERROR', 1);
55
- this.name = 'ValidationError';
56
- }
57
- }
58
-
59
- /**
60
- * Error thrown when file operations fail
61
- */
62
- export class FileSystemError extends CliError {
63
- constructor(
64
- message: string,
65
- public readonly filePath?: string
66
- ) {
67
- super(message, 'FILE_SYSTEM_ERROR', 1);
68
- this.name = 'FileSystemError';
69
- }
70
- }
71
-
72
- /**
73
- * Error thrown when external service calls fail
74
- */
75
- export class ExternalServiceError extends CliError {
76
- constructor(
77
- message: string,
78
- public readonly service?: string
79
- ) {
80
- super(message, 'EXTERNAL_SERVICE_ERROR', 1);
81
- this.name = 'ExternalServiceError';
82
- }
83
- }
@@ -1 +0,0 @@
1
- export * from './cli-error.js';
package/src/index.ts DELETED
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env node
2
- import { type ChildProcess, spawn } from 'child_process';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
8
-
9
- const packageName =
10
- path.basename(process.argv[1] ?? '') === 'index'
11
- ? 'poet'
12
- : path.basename(process.argv[1] ?? '') || 'poet';
13
-
14
- let cli: ChildProcess | null = null;
15
- let isShuttingDown = false;
16
- let hasExited = false;
17
-
18
- const cleanup = async (): Promise<void> => {
19
- if (isShuttingDown) return;
20
- isShuttingDown = true;
21
-
22
- if (cli && !cli.killed) {
23
- try {
24
- cli.kill('SIGTERM');
25
-
26
- await new Promise<void>((resolve) => {
27
- const timeout = setTimeout(() => {
28
- if (cli && !cli.killed) {
29
- cli.kill('SIGKILL');
30
- }
31
- resolve();
32
- }, 5000);
33
-
34
- cli?.once('exit', () => {
35
- clearTimeout(timeout);
36
- resolve();
37
- });
38
- });
39
- } catch (_error) {
40
- // ignore
41
- }
42
- }
43
- };
44
-
45
- const exitProcess = (code: number) => {
46
- if (hasExited) return;
47
- hasExited = true;
48
- process.exit(code);
49
- };
50
-
51
- const killSignals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP'];
52
- killSignals.forEach((signal) => {
53
- process.on(signal, async () => {
54
- await cleanup();
55
- exitProcess(0);
56
- });
57
- });
58
-
59
- process.on('uncaughtException', async () => {
60
- await cleanup();
61
- exitProcess(1);
62
- });
63
-
64
- process.on('unhandledRejection', async () => {
65
- await cleanup();
66
- exitProcess(1);
67
- });
68
-
69
- try {
70
- cli = spawn(
71
- 'node',
72
- ['--no-deprecation', path.join(__dirname, '../bin/start'), ...process.argv.slice(2)],
73
- {
74
- stdio: 'inherit',
75
- env: {
76
- ...process.env,
77
- POETORA_PACKAGE_NAME: packageName,
78
- CLI_TEST_MODE: process.env.CLI_TEST_MODE ?? 'false',
79
- },
80
- shell: process.platform === 'win32',
81
- windowsHide: process.platform === 'win32',
82
- detached: false,
83
- }
84
- );
85
-
86
- cli.on('error', async (error) => {
87
- console.error(`Failed to start ${packageName}: ${error.message}`);
88
- await cleanup();
89
- exitProcess(1);
90
- });
91
-
92
- cli.on('exit', (code) => {
93
- exitProcess(code ?? 0);
94
- });
95
- } catch (error) {
96
- console.error(`Failed to start ${packageName}: ${error}`);
97
- exitProcess(1);
98
- }
99
-
100
- process.on('exit', () => {
101
- if (cli && !cli.killed) {
102
- try {
103
- cli.kill('SIGKILL');
104
- } catch (_error) {
105
- // ignore
106
- }
107
- }
108
- });
109
-
110
- export { cli };
@@ -1,132 +0,0 @@
1
- import { categorizeFilePaths, getPoetIgnore } from '@poetora/prebuild';
2
- import { coreRemark } from '@poetora/shared';
3
- import fs from 'fs';
4
- import type { Node, Root, Text } from 'mdast';
5
- import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
6
- import path from 'path';
7
- import { visit } from 'unist-util-visit';
8
-
9
- export interface AccessibilityFixAttribute {
10
- filePath: string;
11
- line?: number;
12
- column?: number;
13
- element: 'img' | 'video' | 'a';
14
- tagName: string;
15
- }
16
-
17
- export interface MdxAccessibilityResult {
18
- missingAltAttributes: AccessibilityFixAttribute[];
19
- totalFiles: number;
20
- filesWithIssues: number;
21
- }
22
-
23
- const checkAltAttributes = (filePath: string, content: string): AccessibilityFixAttribute[] => {
24
- const issues: AccessibilityFixAttribute[] = [];
25
-
26
- const visitElements = () => {
27
- return (tree: Root) => {
28
- visit(tree, (node) => {
29
- if (node.type === 'image') {
30
- if (!node.alt || node.alt.trim() === '') {
31
- issues.push({
32
- filePath,
33
- line: node.position?.start.line,
34
- column: node.position?.start.column,
35
- element: 'img',
36
- tagName: 'image (markdown)',
37
- });
38
- }
39
- return;
40
- }
41
-
42
- const mdxJsxElement = node as MdxJsxFlowElement;
43
- if (mdxJsxElement.name === 'img' || mdxJsxElement.name === 'video') {
44
- const altAttrIndex = mdxJsxElement.attributes.findIndex(
45
- (attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'alt'
46
- );
47
-
48
- const altAttribute = mdxJsxElement.attributes[altAttrIndex];
49
- const hasValidAlt =
50
- altAttribute &&
51
- typeof altAttribute.value === 'string' &&
52
- altAttribute.value.trim() !== '';
53
-
54
- if (!hasValidAlt) {
55
- issues.push({
56
- filePath,
57
- line: node.position?.start.line,
58
- column: node.position?.start.column,
59
- element: mdxJsxElement.name,
60
- tagName: mdxJsxElement.name,
61
- });
62
- }
63
- } else if (mdxJsxElement.name === 'a') {
64
- const hasTextContent = (children: Node[]): boolean => {
65
- return children.some((child) => {
66
- if (child.type === 'text') {
67
- const textNode = child as Text;
68
- return textNode.value.trim() !== '';
69
- }
70
- if ('children' in child && Array.isArray(child.children)) {
71
- return hasTextContent(child.children as Node[]);
72
- }
73
- return false;
74
- });
75
- };
76
-
77
- if (!hasTextContent(mdxJsxElement.children as Node[])) {
78
- issues.push({
79
- filePath,
80
- line: node.position?.start.line,
81
- column: node.position?.start.column,
82
- element: 'a',
83
- tagName: '<a>',
84
- });
85
- }
86
- }
87
- });
88
- return tree;
89
- };
90
- };
91
-
92
- try {
93
- coreRemark().use(visitElements).processSync(content);
94
- } catch (error) {
95
- console.warn(`Warning: Could not parse ${filePath}: ${error}`);
96
- }
97
-
98
- return issues;
99
- };
100
-
101
- export const checkMdxAccessibility = async (
102
- baseDir: string = process.cwd()
103
- ): Promise<MdxAccessibilityResult> => {
104
- const poetIgnore = await getPoetIgnore(baseDir);
105
- const { contentFilenames } = await categorizeFilePaths(baseDir, poetIgnore);
106
- const mdxFiles: string[] = [];
107
- for (const file of contentFilenames) {
108
- mdxFiles.push(path.join(baseDir, file));
109
- }
110
- const allIssues: AccessibilityFixAttribute[] = [];
111
- const filesWithIssues = new Set<string>();
112
-
113
- for (const filePath of mdxFiles) {
114
- try {
115
- const content = fs.readFileSync(filePath, 'utf-8');
116
- const issues = checkAltAttributes(filePath, content);
117
-
118
- if (issues.length > 0) {
119
- allIssues.push(...issues);
120
- filesWithIssues.add(filePath);
121
- }
122
- } catch (error) {
123
- console.warn(`Warning: Could not read file ${filePath}: ${error}`);
124
- }
125
- }
126
-
127
- return {
128
- missingAltAttributes: allIssues,
129
- totalFiles: mdxFiles.length,
130
- filesWithIssues: filesWithIssues.size,
131
- };
132
- };
@@ -1,73 +0,0 @@
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,83 +0,0 @@
1
- import detect from 'detect-port';
2
- import { beforeEach, describe, expect, it, vi } from 'vitest';
3
-
4
- import { NoAvailablePortError } from '../../errors/index.js';
5
- import { CLI_CONSTANTS } from '../../types/index.js';
6
- import { PortService } from '../port.service.js';
7
-
8
- vi.mock('detect-port');
9
-
10
- describe('PortService', () => {
11
- let service: PortService;
12
- const mockDetect = vi.mocked(detect);
13
-
14
- beforeEach(() => {
15
- service = new PortService();
16
- vi.clearAllMocks();
17
- });
18
-
19
- describe('findAvailablePort', () => {
20
- it('should return preferred port when available', async () => {
21
- mockDetect.mockResolvedValue(3000);
22
-
23
- const port = await service.findAvailablePort(3000);
24
-
25
- expect(port).toBe(3000);
26
- expect(mockDetect).toHaveBeenCalledWith(3000);
27
- });
28
-
29
- it('should return default port when no preferred port specified', async () => {
30
- mockDetect.mockResolvedValue(CLI_CONSTANTS.PORT.DEFAULT);
31
-
32
- const port = await service.findAvailablePort();
33
-
34
- expect(port).toBe(CLI_CONSTANTS.PORT.DEFAULT);
35
- });
36
-
37
- it('should try next port when first is occupied', async () => {
38
- mockDetect.mockResolvedValueOnce(3001); // 3000 occupied
39
- mockDetect.mockResolvedValueOnce(3001); // 3001 available
40
-
41
- const port = await service.findAvailablePort(3000);
42
-
43
- expect(port).toBe(3001);
44
- expect(mockDetect).toHaveBeenCalledTimes(2);
45
- expect(mockDetect).toHaveBeenNthCalledWith(1, 3000);
46
- expect(mockDetect).toHaveBeenNthCalledWith(2, 3001);
47
- });
48
-
49
- it('should try multiple ports until finding available one', async () => {
50
- // Ports 3000, 3001, 3002 are occupied
51
- mockDetect.mockResolvedValueOnce(3001);
52
- mockDetect.mockResolvedValueOnce(3002);
53
- mockDetect.mockResolvedValueOnce(3003);
54
- // Port 3003 is available
55
- mockDetect.mockResolvedValueOnce(3003);
56
-
57
- const port = await service.findAvailablePort(3000);
58
-
59
- expect(port).toBe(3003);
60
- expect(mockDetect).toHaveBeenCalledTimes(4);
61
- });
62
-
63
- it('should throw NoAvailablePortError after MAX_ATTEMPTS', async () => {
64
- // All ports are occupied
65
- mockDetect.mockImplementation((port) => Promise.resolve((port as number) + 1));
66
-
67
- await expect(service.findAvailablePort(3000)).rejects.toThrow(NoAvailablePortError);
68
- expect(mockDetect).toHaveBeenCalledTimes(CLI_CONSTANTS.PORT.MAX_ATTEMPTS);
69
- });
70
-
71
- it('should include start port in error message', async () => {
72
- mockDetect.mockImplementation((port) => Promise.resolve((port as number) + 1));
73
-
74
- try {
75
- await service.findAvailablePort(5000);
76
- expect.fail('Should have thrown error');
77
- } catch (error) {
78
- expect(error).toBeInstanceOf(NoAvailablePortError);
79
- expect((error as NoAvailablePortError).message).toContain('5000');
80
- }
81
- });
82
- });
83
- });