@tixyel/cli 1.0.0 → 2.0.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.
@@ -1,146 +0,0 @@
1
- import { resolve, basename, join } from 'path';
2
- import { mkdir, writeFile } from 'fs/promises';
3
- import { existsSync } from 'fs';
4
- import inquirer from 'inquirer';
5
- import { loadCliConfig } from '../utils/load-cli-config.js';
6
- /**
7
- * Determines if a path should be treated as a parent directory or target directory
8
- * Paths ending with / are treated as target directories
9
- */
10
- function isTargetDirectory(path) {
11
- return path.endsWith('/') || path.endsWith('\\');
12
- }
13
- /**
14
- * Gets the next widget number in a parent directory
15
- */
16
- async function getNextWidgetNumber(parentPath) {
17
- try {
18
- if (!existsSync(parentPath)) {
19
- return '01';
20
- }
21
- // Check for existing widget folders
22
- const { readdirSync } = await import('fs');
23
- const entries = readdirSync(parentPath);
24
- const widgetNumbers = entries
25
- .filter((name) => /^\d+\s*-\s*/.test(name))
26
- .map((name) => parseInt(name.split('-')[0], 10))
27
- .filter((num) => !isNaN(num));
28
- const maxNum = widgetNumbers.length > 0 ? Math.max(...widgetNumbers) : 0;
29
- return String(maxNum + 1).padStart(2, '0');
30
- }
31
- catch {
32
- return '01';
33
- }
34
- }
35
- /**
36
- * Generate command handler
37
- *
38
- * Usage:
39
- * - `tixyel generate` -> creates widget in current directory (01 - Widget)
40
- * - `tixyel generate widgets` -> creates widgets/01 - Widget/
41
- * - `tixyel generate widgets myname` -> creates widgets/myname/
42
- * - `tixyel generate widgets/` -> creates widget at widgets/
43
- * - All parameters except path are optional and can be provided via prompts
44
- */
45
- export async function generateCommand(targetPath, widgetName, description, tags) {
46
- const rootPath = process.cwd();
47
- // Load CLI config
48
- const cliConfig = await loadCliConfig(rootPath);
49
- // Default to current directory if no path provided
50
- const resolvedPath = targetPath ? resolve(rootPath, targetPath.replace(/[/\\]$/, '')) : rootPath;
51
- const isTarget = targetPath ? isTargetDirectory(targetPath) : false;
52
- console.log('šŸŽØ Generating widget...\n');
53
- if (isTarget) {
54
- // Path ends with / -> use as target directory
55
- const folderName = basename(resolvedPath);
56
- await createWidget(resolvedPath, folderName, description, tags, cliConfig);
57
- }
58
- else {
59
- // Path without / -> use as parent directory
60
- let finalWidgetName = widgetName;
61
- if (!finalWidgetName) {
62
- // Get next number and ask for name
63
- const nextNum = await getNextWidgetNumber(resolvedPath);
64
- const defaultName = `${nextNum} - Widget`;
65
- const answers = await inquirer.prompt([
66
- {
67
- type: 'input',
68
- name: 'name',
69
- message: 'Widget name:',
70
- default: defaultName,
71
- },
72
- ]);
73
- finalWidgetName = answers.name;
74
- }
75
- const widgetPath = join(resolvedPath, finalWidgetName);
76
- await createWidget(widgetPath, finalWidgetName, description, tags, cliConfig);
77
- }
78
- console.log('\n✨ Generation complete!');
79
- }
80
- /**
81
- * Prompts for optional widget metadata
82
- */
83
- async function promptMetadata(widgetName, initialDescription, initialTags) {
84
- const answers = await inquirer.prompt([
85
- {
86
- type: 'input',
87
- name: 'description',
88
- message: 'Widget description:',
89
- default: initialDescription || '',
90
- },
91
- {
92
- type: 'input',
93
- name: 'tags',
94
- message: 'Widget tags (comma-separated):',
95
- default: initialTags || '',
96
- },
97
- ]);
98
- const tagsArray = answers.tags
99
- ? answers.tags
100
- .split(',')
101
- .map((tag) => tag.trim())
102
- .filter((tag) => tag.length > 0)
103
- : [];
104
- return {
105
- description: answers.description,
106
- tags: tagsArray,
107
- };
108
- }
109
- /**
110
- * Creates a widget with .tixyel configuration file
111
- */
112
- async function createWidget(widgetPath, widgetName, description, tagsStr, cliConfig) {
113
- try {
114
- console.log(`šŸ“ Creating widget: ${widgetName}`);
115
- console.log(` Path: ${widgetPath}`);
116
- // Create directory
117
- await mkdir(widgetPath, { recursive: true });
118
- // Prompt for metadata if not provided
119
- const metadata = await promptMetadata(widgetName, description, tagsStr);
120
- // Create .tixyel configuration
121
- const tixyelConfig = {
122
- name: widgetName,
123
- description: metadata.description,
124
- metadata: {
125
- author: cliConfig?.generationDefaults.author || 'Tixyel',
126
- tags: metadata.tags,
127
- platform: cliConfig?.generationDefaults.platform || 'streamelements',
128
- },
129
- };
130
- const configPath = join(widgetPath, '.tixyel');
131
- await writeFile(configPath, JSON.stringify(tixyelConfig, null, 2), 'utf-8');
132
- console.log(`āœ“ Created ${widgetName} successfully`);
133
- console.log(` - Name: ${widgetName}`);
134
- if (metadata.description) {
135
- console.log(` - Description: ${metadata.description}`);
136
- }
137
- if (metadata.tags.length > 0) {
138
- console.log(` - Tags: ${metadata.tags.join(', ')}`);
139
- }
140
- console.log(` - .tixyel configuration created`);
141
- }
142
- catch (error) {
143
- console.error(`āŒ Failed to create widget: ${error}`);
144
- throw error;
145
- }
146
- }
@@ -1,11 +0,0 @@
1
- import type { Command as CommanderCommand } from 'commander';
2
- import { Command } from './base.js';
3
- /**
4
- * Generate command
5
- */
6
- export declare class GenerateCommand extends Command {
7
- name: string;
8
- description: string;
9
- register(command: CommanderCommand): void;
10
- execute(targetPath?: string, widgetName?: string, description?: string, tags?: string): Promise<void>;
11
- }
@@ -1,198 +0,0 @@
1
- import { resolve, basename, join } from 'path';
2
- import { mkdir, writeFile } from 'fs/promises';
3
- import { existsSync } from 'fs';
4
- import inquirer from 'inquirer';
5
- import { loadCliConfig } from '../utils/load-cli-config.js';
6
- import { validateWorkspaceInit, getConfigPathFromWidget } from '../utils/workspace.js';
7
- import { Command } from './base.js';
8
- /**
9
- * Generate command
10
- */
11
- export class GenerateCommand extends Command {
12
- constructor() {
13
- super(...arguments);
14
- this.name = 'generate';
15
- this.description = 'Generate a new widget (path defaults to current directory)';
16
- }
17
- register(command) {
18
- command
19
- .command(`${this.name} [path] [name] [description] [tags]`)
20
- .description(this.description)
21
- .action((...args) => this.execute(...args));
22
- }
23
- async execute(targetPath, widgetName, description, tags) {
24
- try {
25
- // Validate workspace is initialized
26
- const workspaceRoot = await validateWorkspaceInit();
27
- const rootPath = process.cwd();
28
- // Load CLI config from workspace root
29
- const cliConfig = await loadCliConfig(workspaceRoot);
30
- // Default to current directory if no path provided
31
- const resolvedPath = targetPath ? resolve(rootPath, targetPath.replace(/[/\\]$/, '')) : rootPath;
32
- const isTarget = targetPath ? isTargetDirectory(targetPath) : false;
33
- console.log('šŸŽØ Generating widget...\n');
34
- if (isTarget) {
35
- // Path ends with / -> use as target directory
36
- const folderName = basename(resolvedPath);
37
- await createWidget(resolvedPath, folderName, description, tags, cliConfig, workspaceRoot);
38
- }
39
- else {
40
- // Path without / -> use as parent directory
41
- let finalWidgetName = widgetName;
42
- if (!finalWidgetName) {
43
- // Get next number and ask for name
44
- const nextNum = await getNextWidgetNumber(resolvedPath);
45
- const defaultName = `${nextNum} - Widget`;
46
- const answers = await inquirer.prompt([
47
- {
48
- type: 'input',
49
- name: 'name',
50
- message: 'Widget name:',
51
- default: defaultName,
52
- },
53
- ]);
54
- finalWidgetName = answers.name;
55
- }
56
- const widgetPath = join(resolvedPath, finalWidgetName);
57
- await createWidget(widgetPath, finalWidgetName, description, tags, cliConfig, workspaceRoot);
58
- }
59
- console.log('\n✨ Generation complete!');
60
- }
61
- catch (error) {
62
- console.error(`${error}`);
63
- process.exit(1);
64
- }
65
- }
66
- }
67
- /**
68
- * Determines if a path should be treated as a parent directory or target directory
69
- * Paths ending with / are treated as target directories
70
- */
71
- function isTargetDirectory(path) {
72
- return path.endsWith('/') || path.endsWith('\\');
73
- }
74
- /**
75
- * Gets the next widget number in a parent directory
76
- */
77
- async function getNextWidgetNumber(parentPath) {
78
- try {
79
- if (!existsSync(parentPath)) {
80
- return '01';
81
- }
82
- // Check for existing widget folders
83
- const { readdirSync } = await import('fs');
84
- const entries = readdirSync(parentPath);
85
- const widgetNumbers = entries
86
- .filter((name) => /^\d+\s*-\s*/.test(name))
87
- .map((name) => parseInt(name.split('-')[0], 10))
88
- .filter((num) => !isNaN(num));
89
- const maxNum = widgetNumbers.length > 0 ? Math.max(...widgetNumbers) : 0;
90
- return String(maxNum + 1).padStart(2, '0');
91
- }
92
- catch {
93
- return '01';
94
- }
95
- }
96
- /**
97
- * Prompts for optional widget metadata
98
- */
99
- async function promptMetadata(widgetName, initialDescription, initialTags) {
100
- const answers = await inquirer.prompt([
101
- {
102
- type: 'input',
103
- name: 'description',
104
- message: 'Widget description:',
105
- default: initialDescription || '',
106
- },
107
- {
108
- type: 'input',
109
- name: 'tags',
110
- message: 'Widget tags (comma-separated):',
111
- default: initialTags || '',
112
- },
113
- ]);
114
- const tagsArray = answers.tags
115
- ? answers.tags
116
- .split(',')
117
- .map((tag) => tag.trim())
118
- .filter((tag) => tag.length > 0)
119
- : [];
120
- return {
121
- description: answers.description,
122
- tags: tagsArray,
123
- };
124
- }
125
- /**
126
- * Creates a widget with .tixyel configuration file
127
- */
128
- async function createWidget(widgetPath, widgetName, description, tagsStr, cliConfig, workspaceRoot) {
129
- try {
130
- console.log(`šŸ“ Creating widget: ${widgetName}`);
131
- console.log(` Path: ${widgetPath}`);
132
- // Create directory
133
- await mkdir(widgetPath, { recursive: true });
134
- // Calculate config path if workspace root is provided
135
- const configPath = workspaceRoot ? getConfigPathFromWidget(widgetPath, workspaceRoot) : undefined;
136
- // Prompt for metadata if not provided
137
- const metadata = await promptMetadata(widgetName, description, tagsStr);
138
- // Create .tixyel configuration
139
- const tixyelConfig = {
140
- name: widgetName,
141
- description: metadata.description,
142
- };
143
- if (configPath) {
144
- tixyelConfig.configPath = configPath;
145
- }
146
- tixyelConfig.metadata = {
147
- author: cliConfig?.generationDefaults.author || 'Tixyel',
148
- tags: metadata.tags,
149
- platform: cliConfig?.generationDefaults.platform || 'streamelements',
150
- };
151
- const configFilePath = join(widgetPath, '.tixyel');
152
- await writeFile(configFilePath, JSON.stringify(tixyelConfig, null, 2), 'utf-8');
153
- // Create scaffold files from config
154
- const scaffold = cliConfig?.generationDefaults.scaffold || [];
155
- let createdFiles = 0;
156
- let createdFolders = 0;
157
- async function processScaffoldItem(item, basePath) {
158
- const fullPath = join(basePath, item.name);
159
- if (item.type === 'folder') {
160
- await mkdir(fullPath, { recursive: true });
161
- createdFolders++;
162
- // Process folder contents if any
163
- if (item.content && Array.isArray(item.content)) {
164
- for (const child of item.content) {
165
- await processScaffoldItem(child, fullPath);
166
- }
167
- }
168
- }
169
- else if (item.type === 'file') {
170
- // Write file
171
- await writeFile(fullPath, item.content || '', 'utf-8');
172
- createdFiles++;
173
- }
174
- }
175
- for (const item of scaffold) {
176
- await processScaffoldItem(item, widgetPath);
177
- }
178
- console.log(`āœ“ Created ${widgetName} successfully`);
179
- console.log(` - Name: ${widgetName}`);
180
- if (metadata.description) {
181
- console.log(` - Description: ${metadata.description}`);
182
- }
183
- if (metadata.tags.length > 0) {
184
- console.log(` - Tags: ${metadata.tags.join(', ')}`);
185
- }
186
- if (configPath) {
187
- console.log(` - Config path: ${configPath}`);
188
- }
189
- console.log(` - .tixyel configuration created`);
190
- if (createdFiles > 0 || createdFolders > 0) {
191
- console.log(` - Scaffold: ${createdFiles} file(s), ${createdFolders} folder(s)`);
192
- }
193
- }
194
- catch (error) {
195
- console.error(`āŒ Failed to create widget: ${error}`);
196
- throw error;
197
- }
198
- }
@@ -1,9 +0,0 @@
1
- import { Command } from './base.js';
2
- /**
3
- * Init command - initializes a workspace
4
- */
5
- export declare class InitCommand extends Command {
6
- name: string;
7
- description: string;
8
- execute(): Promise<void>;
9
- }
@@ -1,140 +0,0 @@
1
- import { writeFile } from 'fs/promises';
2
- import { resolve } from 'path';
3
- import { Command } from './base.js';
4
- /**
5
- * Init command - initializes a workspace
6
- */
7
- export class InitCommand extends Command {
8
- constructor() {
9
- super(...arguments);
10
- this.name = 'init';
11
- this.description = 'Initialize a new Tixyel workspace';
12
- }
13
- async execute() {
14
- const rootPath = process.cwd();
15
- const configPath = resolve(rootPath, 'tixyel.config.ts');
16
- console.log('šŸš€ Initializing Tixyel workspace...\n');
17
- console.log(`šŸ“ Workspace root: ${rootPath}\n`);
18
- // Create initial tixyel.config.ts
19
- const configContent = `import type { TixyelCliConfig } from '@tixyel/cli';
20
-
21
- /**
22
- * Tixyel workspace configuration
23
- */
24
- const config: TixyelCliConfig = {
25
- // Search configuration
26
- search: {
27
- maxDepth: 3,
28
- ignore: ['node_modules', 'dist', '.git', '.turbo', '.vscode'],
29
- },
30
-
31
- // Defaults for widget generation
32
- generationDefaults: {
33
- author: 'Your Name',
34
- minify: true,
35
- platform: 'streamelements',
36
-
37
- // File structure to create when generating a widget
38
- scaffold: [
39
- {
40
- name: 'development',
41
- type: 'folder',
42
- content: [
43
- {
44
- name: 'index.html',
45
- type: 'file',
46
- content: '',
47
- },
48
- {
49
- name: 'script.js',
50
- type: 'file',
51
- content: '',
52
- },
53
- {
54
- name: 'fields.json',
55
- type: 'file',
56
- content: '{}',
57
- },
58
- {
59
- name: 'data.json',
60
- type: 'file',
61
- content: '{}',
62
- },
63
- {
64
- name: 'style.css',
65
- type: 'file',
66
- content: '',
67
- },
68
- ],
69
- },
70
- {
71
- name: 'finished',
72
- type: 'folder',
73
- },
74
- ],
75
- },
76
-
77
- // Build configuration
78
- build: {
79
- parallel: false,
80
- verbose: false,
81
- find: {
82
- html: ['index.html'],
83
- css: ['style.css'],
84
- script: ['resources.js', 'script.js'],
85
- fields: ['cf.json', 'fields.json'],
86
- },
87
- finished: {
88
- 'HTML.html': 'html',
89
- 'CSS.css': 'css',
90
- 'SCRIPT.js': 'script',
91
- 'FIELDS.json': 'fields',
92
- },
93
- obfuscation: {
94
- javascript: {
95
- compact: true,
96
- log: false,
97
- debugProtection: false,
98
- selfDefending: false,
99
- deadCodeInjection: false,
100
- controlFlowFlattening: false,
101
- stringArray: false,
102
- simplify: false,
103
- identifierNamesGenerator: 'mangled',
104
- },
105
- css: {
106
- removeNesting: true,
107
- autoprefixer: {
108
- overrideBrowserslist: ['Chrome 127'],
109
- },
110
- cssnano: {
111
- preset: 'default',
112
- },
113
- },
114
- html: {
115
- removeComments: true,
116
- collapseWhitespace: true,
117
- minifyCSS: true,
118
- minifyJS: true,
119
- removeAttributeQuotes: false,
120
- },
121
- },
122
- },
123
- };
124
-
125
- export default config;
126
- `;
127
- try {
128
- await writeFile(configPath, configContent, 'utf-8');
129
- console.log('āœ… Created tixyel.config.ts');
130
- console.log(' Edit it to customize your workspace settings');
131
- console.log('\n✨ Workspace initialized! You can now use:');
132
- console.log(' - tixyel generate [path] - Generate new widgets');
133
- console.log(' - tixyel build - Build selected widgets');
134
- }
135
- catch (error) {
136
- console.error(`āŒ Failed to initialize workspace: ${error}`);
137
- throw error;
138
- }
139
- }
140
- }
@@ -1,149 +0,0 @@
1
- import type { Options as AutoprefixerOptions } from 'autoprefixer';
2
- import type { Options as CssnanoOptions } from 'cssnano';
3
- import type { ObfuscatorOptions } from 'javascript-obfuscator';
4
- import type { Options as HtmlMinifierOptions } from 'html-minifier-terser';
5
- /**
6
- * Schema for tixyel.config.ts configuration file
7
- */
8
- export interface TixyelCliConfig {
9
- /**
10
- * Search configuration
11
- */
12
- search?: {
13
- /**
14
- * Maximum depth to search for .tixyel files
15
- */
16
- maxDepth?: number;
17
- /**
18
- * Folders to ignore during search
19
- */
20
- ignore?: string[];
21
- };
22
- /**
23
- * Defaults for generating new widgets
24
- */
25
- generationDefaults?: {
26
- /**
27
- * Default author for new widgets
28
- */
29
- author?: string;
30
- /**
31
- * Default minify setting
32
- */
33
- minify?: boolean;
34
- /**
35
- * Default platform
36
- */
37
- platform?: 'streamelements';
38
- /**
39
- * File/folder structure to create when generating a widget
40
- */
41
- scaffold?: ScaffoldItem[];
42
- };
43
- /**
44
- * Build configuration
45
- */
46
- build?: {
47
- /**
48
- * Run builds in parallel
49
- */
50
- parallel?: boolean;
51
- /**
52
- * Verbose output
53
- */
54
- verbose?: boolean;
55
- /**
56
- * Default file patterns for widgets
57
- */
58
- find?: {
59
- html?: string[];
60
- css?: string[];
61
- script?: string[];
62
- fields?: string[];
63
- };
64
- /**
65
- * Default output file mapping
66
- */
67
- finished?: {
68
- [key: string]: 'html' | 'css' | 'script' | 'fields';
69
- };
70
- /**
71
- * Obfuscation options per file type
72
- */
73
- obfuscation?: {
74
- /**
75
- * JavaScript obfuscation options
76
- */
77
- javascript?: ObfuscatorOptions;
78
- /**
79
- * CSS processing options
80
- */
81
- css?: {
82
- removeNesting?: boolean;
83
- autoprefixer?: AutoprefixerOptions;
84
- cssnano?: CssnanoOptions;
85
- };
86
- /**
87
- * HTML minification options
88
- */
89
- html?: HtmlMinifierOptions;
90
- };
91
- };
92
- }
93
- /**
94
- * Represents a file or folder in the scaffold
95
- */
96
- export type ScaffoldItem = ScaffoldFile | ScaffoldFolder;
97
- /**
98
- * File in scaffold
99
- */
100
- export interface ScaffoldFile {
101
- name: string;
102
- type: 'file';
103
- content: string;
104
- }
105
- /**
106
- * Folder in scaffold
107
- */
108
- export interface ScaffoldFolder {
109
- name: string;
110
- type: 'folder';
111
- content?: ScaffoldItem[];
112
- }
113
- /**
114
- * Required configuration with all defaults
115
- */
116
- export interface RequiredCliConfig {
117
- search: Required<NonNullable<TixyelCliConfig['search']>>;
118
- generationDefaults: Required<NonNullable<TixyelCliConfig['generationDefaults']>>;
119
- build: {
120
- parallel: boolean;
121
- verbose: boolean;
122
- find: {
123
- html: string[];
124
- css: string[];
125
- script: string[];
126
- fields: string[];
127
- };
128
- finished: {
129
- [key: string]: 'html' | 'css' | 'script' | 'fields';
130
- };
131
- obfuscation: {
132
- javascript: ObfuscatorOptions;
133
- css: {
134
- removeNesting: boolean;
135
- autoprefixer: AutoprefixerOptions;
136
- cssnano: CssnanoOptions;
137
- };
138
- html: HtmlMinifierOptions;
139
- };
140
- };
141
- }
142
- /**
143
- * Default CLI configuration
144
- */
145
- export declare const DEFAULT_CLI_CONFIG: RequiredCliConfig;
146
- /**
147
- * Merges user config with defaults
148
- */
149
- export declare function mergeCliConfig(userConfig: TixyelCliConfig | undefined): RequiredCliConfig;