@tixyel/cli 2.0.2 → 2.2.0

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/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  export * from './workspace.js';
3
+ export * from './widget.js';
package/dist/index.js CHANGED
@@ -1,12 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { build, createWidget, findWidgets, getNextWidgetNumber } from './widget.js';
3
- import { loadWorkspace, validateWorkspace } from './workspace.js';
4
- import { workspace_config } from './templates/workspace.js';
5
2
  import { Command as Commander } from 'commander';
6
3
  import { basename, join, resolve } from 'path';
7
4
  import { writeFile } from 'fs/promises';
8
5
  import { createRequire } from 'module';
9
- import inquirer from 'inquirer';
10
6
  const program = new Commander();
11
7
  program
12
8
  .name('tixyel')
@@ -26,6 +22,7 @@ program
26
22
  .aliases(['initialize', 'i', 'setup', 'start'])
27
23
  .description('Initialize a new widget workspace.')
28
24
  .action(async () => {
25
+ const { workspace_config } = await import('./templates/workspace.js');
29
26
  const root = process.cwd();
30
27
  const config = resolve(root, 'tixyel.config.ts');
31
28
  console.log('🚀 Initializing new workspace...');
@@ -49,6 +46,8 @@ program
49
46
  .description('Generate a new widget.')
50
47
  .action(async (path, name, description, tags) => {
51
48
  try {
49
+ const { validateWorkspace, loadWorkspace } = await import('./workspace.js');
50
+ const { createWidget, getNextWidgetNumber } = await import('./widget.js');
52
51
  // Validate if the workspace is initialized
53
52
  const validWorkspacePath = await validateWorkspace();
54
53
  const rootPath = process.cwd();
@@ -74,6 +73,7 @@ program
74
73
  // Get next number and ask for name
75
74
  const nextNum = await getNextWidgetNumber(resolvedPath);
76
75
  const defaultName = `${nextNum} - Widget`;
76
+ const inquirer = (await import('inquirer')).default;
77
77
  const answers = await inquirer.prompt([
78
78
  {
79
79
  type: 'input',
@@ -86,7 +86,7 @@ program
86
86
  }
87
87
  const widgetPath = join(resolvedPath, finalWidgetName);
88
88
  await createWidget(widgetPath, {
89
- name: finalWidgetName,
89
+ name: finalWidgetName.replace(/^\d+\s*-\s*/, ''),
90
90
  description,
91
91
  tags: tags ? tags.split(',').map((t) => t.trim()) : undefined,
92
92
  }, workspaceConfig, validWorkspacePath);
@@ -104,8 +104,14 @@ program
104
104
  .option('-d --depth <number>', 'Maximum search depth for widgets', '3')
105
105
  .option('-p --parallel', 'Build widgets in parallel')
106
106
  .option('-v --verbose', 'Show verbose output')
107
+ .option('-w --widgets <names>', 'Widget names or paths to build (comma-separated, or * for all)')
108
+ .option('--bump <type>', 'Version bump type (none, patch, minor, major)')
107
109
  .action(async (options = {}) => {
108
110
  try {
111
+ const { validateWorkspace, loadWorkspace } = await import('./workspace.js');
112
+ const { build, findWidgets } = await import('./widget.js');
113
+ let inquirer = null;
114
+ const getInquirer = async () => (inquirer ??= (await import('inquirer')).default);
109
115
  // Validate if the workspace is initialized
110
116
  const validWorkspacePath = await validateWorkspace();
111
117
  const rootPath = process.cwd();
@@ -115,55 +121,92 @@ program
115
121
  console.log(`🔍 Searching for widgets (max depth: ${maxDepth})...\n`);
116
122
  // Find all widgets on the workspace
117
123
  const widgets = await findWidgets(rootPath, maxDepth, workspaceConfig.search?.ignore || []);
118
- const choices = widgets.map((widget) => ({
119
- name: `${widget.config.name} (${widget.relativePath})`,
120
- value: widget.path,
121
- checked: false,
122
- }));
123
124
  let selectedPaths = [];
124
125
  if (widgets.length === 0) {
125
126
  console.log('❌ No widgets found with .tixyel configuration files.');
126
127
  return;
127
128
  }
128
- else if (widgets.length === 1) {
129
- selectedPaths = [choices[0].value];
130
- console.log(`🔒 Only one widget found, auto-selected: ${choices[0].name}\n`);
129
+ // Handle --widgets option
130
+ if (options.widgets) {
131
+ if (options.widgets === '*') {
132
+ // Select all widgets
133
+ selectedPaths = widgets.map((w) => w.path);
134
+ console.log(`✅ Auto-selected all ${widgets.length} widget(s)\n`);
135
+ }
136
+ else {
137
+ // Parse comma-separated widget names or paths
138
+ const widgetSelectors = options.widgets.split(',').map((s) => s.trim());
139
+ selectedPaths = widgets.filter((w) => widgetSelectors.some((sel) => w.config.name === sel || w.path.includes(sel))).map((w) => w.path);
140
+ if (selectedPaths.length === 0) {
141
+ console.log(`❌ No widgets matched the provided names/paths: ${options.widgets}`);
142
+ return;
143
+ }
144
+ console.log(`✅ Selected ${selectedPaths.length} widget(s)\n`);
145
+ }
131
146
  }
132
147
  else {
133
- console.log(`✅ Found ${widgets.length} widget(s)\n`);
134
- const answers = await inquirer.prompt([
135
- {
136
- type: 'checkbox',
137
- name: 'selectedWidgets',
138
- message: 'Select widgets to build:',
139
- choices,
140
- pageSize: 10,
141
- loop: false,
142
- },
143
- ]);
144
- selectedPaths = answers.selectedWidgets;
148
+ // Interactive mode
149
+ const choices = widgets.map((widget) => ({
150
+ name: `${widget.config.name} (${widget.relativePath})`,
151
+ value: widget.path,
152
+ checked: false,
153
+ }));
154
+ if (widgets.length === 1) {
155
+ selectedPaths = [choices[0].value];
156
+ console.log(`🔒 Only one widget found, auto-selected: ${choices[0].name}\n`);
157
+ }
158
+ else {
159
+ console.log(`✅ Found ${widgets.length} widget(s)\n`);
160
+ const inquirerInstance = await getInquirer();
161
+ const answers = await inquirerInstance.prompt([
162
+ {
163
+ type: 'checkbox',
164
+ name: 'selectedWidgets',
165
+ message: 'Select widgets to build:',
166
+ choices,
167
+ pageSize: 10,
168
+ loop: false,
169
+ },
170
+ ]);
171
+ selectedPaths = answers.selectedWidgets;
172
+ }
145
173
  }
146
174
  if (selectedPaths.length === 0) {
147
175
  console.log('❌ No widgets selected for build. Exiting.');
148
176
  return;
149
177
  }
150
- const versionAnswers = await inquirer.prompt([
151
- {
152
- type: 'select',
153
- name: 'versionBump',
154
- message: 'Select version bump type:',
155
- choices: [
156
- { name: 'No version bump', value: 'none' },
157
- { name: 'Patch (x.x.1)', value: 'patch' },
158
- { name: 'Minor (x.1.x)', value: 'minor' },
159
- { name: 'Major (1.x.x)', value: 'major' },
160
- ],
161
- default: 'none',
162
- loop: false,
163
- pageSize: 4,
164
- },
165
- ]);
166
- const versionBump = versionAnswers.versionBump;
178
+ // Handle version bump
179
+ let versionBump = 'none';
180
+ if (options.bump) {
181
+ const validBumps = ['none', 'patch', 'minor', 'major'];
182
+ if (!validBumps.includes(options.bump)) {
183
+ console.log(`❌ Invalid version bump type: ${options.bump}. Valid options: ${validBumps.join(', ')}`);
184
+ return;
185
+ }
186
+ versionBump = options.bump;
187
+ console.log(`📌 Version bump: ${versionBump}\n`);
188
+ }
189
+ else {
190
+ // Interactive mode
191
+ const inquirerInstance = await getInquirer();
192
+ const versionAnswers = await inquirerInstance.prompt([
193
+ {
194
+ type: 'select',
195
+ name: 'versionBump',
196
+ message: 'Select version bump type:',
197
+ choices: [
198
+ { name: 'No version bump', value: 'none' },
199
+ { name: 'Patch (x.x.1)', value: 'patch' },
200
+ { name: 'Minor (x.1.x)', value: 'minor' },
201
+ { name: 'Major (1.x.x)', value: 'major' },
202
+ ],
203
+ default: 'none',
204
+ loop: false,
205
+ pageSize: 4,
206
+ },
207
+ ]);
208
+ versionBump = versionAnswers.versionBump;
209
+ }
167
210
  // Resolve parallel option (CLI option overrides config)
168
211
  const buildInParallel = options.parallel ?? workspaceConfig.build?.parallel ?? false;
169
212
  const verboseOutput = options.verbose ?? workspaceConfig.build?.verbose ?? false;
@@ -192,3 +235,4 @@ program
192
235
  });
193
236
  program.parse();
194
237
  export * from './workspace.js';
238
+ export * from './widget.js';
package/dist/widget.js CHANGED
@@ -3,8 +3,10 @@ import { minify as minifyHTML } from 'html-minifier-terser';
3
3
  import JavaScriptObfuscator from 'javascript-obfuscator';
4
4
  import { dirname, join, relative, resolve } from 'path';
5
5
  import { readFile as readFilePromise } from 'fs/promises';
6
+ import { renderToStaticMarkup } from 'react-dom/server';
6
7
  import { mkdir, writeFile } from 'fs/promises';
7
8
  import autoprefixer from 'autoprefixer';
9
+ import { isValidElement } from 'react';
8
10
  import { parse } from 'jsonc-parser';
9
11
  import nested from 'postcss-nested';
10
12
  import FastGlob from 'fast-glob';
@@ -12,6 +14,7 @@ import inquirer from 'inquirer';
12
14
  import postcss from 'postcss';
13
15
  import cssnano from 'cssnano';
14
16
  import JSZip from 'jszip';
17
+ import { transformSync } from 'esbuild';
15
18
  export async function createWidget(path, metadata, config, root) {
16
19
  try {
17
20
  console.log(`📁 Creating ${metadata?.name} at: ${path}`);
@@ -43,6 +46,17 @@ export async function createWidget(path, metadata, config, root) {
43
46
  // Create scaffold files from the workspace config
44
47
  const scaffold = config.scaffold || [];
45
48
  let created = { files: 0, folders: 0 };
49
+ async function serializeScaffoldContent(content) {
50
+ if (content === undefined || content === null)
51
+ return '';
52
+ if (typeof content === 'string')
53
+ return content;
54
+ if (isValidElement(content)) {
55
+ return renderToStaticMarkup(content);
56
+ }
57
+ // Fallback to string conversion for unexpected types
58
+ return String(content ?? '');
59
+ }
46
60
  async function processScaffoldItem(item, basePath) {
47
61
  const fullPath = resolve(basePath, item.name);
48
62
  if (item.type === 'folder') {
@@ -56,8 +70,8 @@ export async function createWidget(path, metadata, config, root) {
56
70
  }
57
71
  }
58
72
  else if (item.type === 'file') {
59
- // Write file
60
- await writeFile(fullPath, item.content || '', 'utf-8');
73
+ const content = await serializeScaffoldContent(item.content);
74
+ await writeFile(fullPath, content, 'utf-8');
61
75
  created.files++;
62
76
  }
63
77
  }
@@ -244,6 +258,7 @@ export async function processBuild(widget, workspaceConfig, verbose = false) {
244
258
  const findPatterns = workspaceConfig.build?.find || {
245
259
  html: ['index.html'],
246
260
  script: ['script.js'],
261
+ typescript: ['script.ts'],
247
262
  css: ['styles.css'],
248
263
  fields: ['fields.json'],
249
264
  };
@@ -398,6 +413,30 @@ export async function processBuild(widget, workspaceConfig, verbose = false) {
398
413
  }
399
414
  result += mergedJS.trim();
400
415
  }
416
+ else if (['typescript', 'ts'].some((k) => key === k)) {
417
+ result += watermark.script + '\n';
418
+ if (verbose)
419
+ console.log(` - Processing TypeScript...`);
420
+ const files = findAndRead(entryDir, list);
421
+ let mergedTS = '';
422
+ for await (const content of files) {
423
+ try {
424
+ const transpiled = transformSync(content, {
425
+ loader: 'ts',
426
+ target: 'es2021',
427
+ format: 'iife',
428
+ });
429
+ mergedTS += transpiled.code + '\n';
430
+ }
431
+ catch (error) {
432
+ console.warn(` ⚠️ Failed to compile TypeScript: ${error}`);
433
+ throw error;
434
+ }
435
+ }
436
+ // Obfuscate the compiled JavaScript
437
+ const obfuscated = JavaScriptObfuscator.obfuscate(mergedTS.trim(), workspaceConfig.build?.obfuscation?.javascript);
438
+ result += obfuscated.getObfuscatedCode();
439
+ }
401
440
  else if (['fields', 'FIELDS', 'fielddata', 'fieldData'].some((k) => key === k)) {
402
441
  if (verbose)
403
442
  console.log(` - Processing Json...`);
@@ -2,11 +2,12 @@ import type { Options as HtmlMinifierOptions } from 'html-minifier-terser';
2
2
  import type { ObfuscatorOptions } from 'javascript-obfuscator';
3
3
  import type autoprefixer from 'autoprefixer';
4
4
  import type cssnanoPlugin from 'cssnano';
5
+ import type { JSX } from 'react';
5
6
  export type ScaffoldItem = ScaffoldFile | ScaffoldFolder;
6
7
  export interface ScaffoldFile {
7
8
  name: string;
8
9
  type: 'file';
9
- content: string;
10
+ content: string | JSX.Element;
10
11
  }
11
12
  export interface ScaffoldFolder {
12
13
  name: string;
@@ -105,6 +106,7 @@ export interface WorkspaceConfig<Find extends BuildFindMap = BuildFindMap> {
105
106
  type BuildFindMap = Record<string, string[]>;
106
107
  type BuildResultMap<Find extends BuildFindMap> = Record<string, keyof Find>;
107
108
  export declare function defineWorkspaceConfig<const Find extends BuildFindMap>(config: WorkspaceConfig<Find>): WorkspaceConfig<Find>;
109
+ export declare function resolveConfig(path: string): Promise<WorkspaceConfig>;
108
110
  export declare function findWorkspaceRoot(startPath?: string): Promise<string | null>;
109
111
  export declare function validateWorkspace(): Promise<string>;
110
112
  export declare function loadWorkspace(path: string): Promise<WorkspaceConfig<BuildFindMap>>;
package/dist/workspace.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
2
+ import { extname, resolve } from 'path';
2
3
  import { transform } from 'esbuild';
3
- import { resolve } from 'path';
4
4
  const DEFAULT_WORKSPACE_CONFIG = {
5
5
  search: {
6
6
  maxDepth: 3,
@@ -111,14 +111,51 @@ export function defineWorkspaceConfig(config) {
111
111
  config.scaffold = config.scaffold || DEFAULT_WORKSPACE_CONFIG.scaffold;
112
112
  return config;
113
113
  }
114
+ export async function resolveConfig(path) {
115
+ const configTs = resolve(path, 'tixyel.config.ts');
116
+ const configTsx = resolve(path, 'tixyel.config.tsx');
117
+ const configJs = resolve(path, 'tixyel.config.js');
118
+ const configJsx = resolve(path, 'tixyel.config.jsx');
119
+ const configMjs = resolve(path, 'tixyel.config.mjs');
120
+ let config;
121
+ try {
122
+ // Trying .mjs first (already ES module)
123
+ if (existsSync(configMjs)) {
124
+ const module = await import(`file://${configMjs}`);
125
+ config = module.default || module.config;
126
+ }
127
+ // Trying .js/.jsx (if package.json has type: module)
128
+ else if (existsSync(configJs)) {
129
+ const module = await import(`file://${configJs}`);
130
+ config = module.default || module.config;
131
+ }
132
+ else if (existsSync(configJsx)) {
133
+ config = await loadTsConfig(configJsx, path);
134
+ }
135
+ // Trying .ts/.tsx file (compile on-the-fly)
136
+ else if (existsSync(configTs)) {
137
+ config = await loadTsConfig(configTs, path);
138
+ }
139
+ else if (existsSync(configTsx)) {
140
+ config = await loadTsConfig(configTsx, path);
141
+ }
142
+ }
143
+ catch (error) {
144
+ console.warn(`⚠️ Failed to load tixyel.config: ${error}`);
145
+ throw error;
146
+ }
147
+ return merge(config);
148
+ }
114
149
  export async function findWorkspaceRoot(startPath = process.cwd()) {
115
150
  let currentPath = resolve(startPath);
116
151
  // Limit search to 10 levels up to avoid infinite loops
117
152
  for (let i = 0; i < 10; i++) {
118
153
  const configTs = resolve(currentPath, 'tixyel.config.ts');
154
+ const configTsx = resolve(currentPath, 'tixyel.config.tsx');
119
155
  const configJs = resolve(currentPath, 'tixyel.config.js');
156
+ const configJsx = resolve(currentPath, 'tixyel.config.jsx');
120
157
  const configPathMjs = resolve(currentPath, 'tixyel.config.mjs');
121
- if (existsSync(configTs) || existsSync(configJs) || existsSync(configPathMjs)) {
158
+ if (existsSync(configTs) || existsSync(configTsx) || existsSync(configJs) || existsSync(configJsx) || existsSync(configPathMjs)) {
122
159
  return currentPath;
123
160
  }
124
161
  const parentPath = resolve(currentPath, '..');
@@ -140,10 +177,13 @@ export async function validateWorkspace() {
140
177
  async function loadTsConfig(path, root) {
141
178
  const temp = resolve(root, '.tixyel.config.temp.mjs');
142
179
  const tsContent = readFileSync(path, 'utf-8');
180
+ const extension = extname(path).toLowerCase();
181
+ const loader = extension === '.tsx' ? 'tsx' : extension === '.jsx' ? 'jsx' : 'ts';
143
182
  const { code } = await transform(tsContent, {
144
- loader: 'ts',
183
+ loader,
145
184
  format: 'esm',
146
185
  target: 'es2022',
186
+ ...(loader === 'tsx' || loader === 'jsx' ? { jsx: 'automatic' } : {}),
147
187
  });
148
188
  writeFileSync(temp, code, 'utf-8');
149
189
  try {
@@ -158,31 +198,7 @@ async function loadTsConfig(path, root) {
158
198
  }
159
199
  }
160
200
  export async function loadWorkspace(path) {
161
- const configPathTs = resolve(path, 'tixyel.config.ts');
162
- const configPathJs = resolve(path, 'tixyel.config.js');
163
- const configPathMjs = resolve(path, 'tixyel.config.mjs');
164
- let config;
165
- try {
166
- // Trying .mjs first (already ES module)
167
- if (existsSync(configPathMjs)) {
168
- const module = await import(`file://${configPathMjs}`);
169
- config = module.default || module.config;
170
- }
171
- // Trying .js (if package.json has type: module)
172
- else if (existsSync(configPathJs)) {
173
- const module = await import(`file://${configPathJs}`);
174
- config = module.default || module.config;
175
- }
176
- // Trying .ts file (compile on-the-fly)
177
- else if (existsSync(configPathTs)) {
178
- config = await loadTsConfig(configPathTs, path);
179
- }
180
- }
181
- catch (error) {
182
- console.warn(`⚠️ Failed to load tixyel.config: ${error}`);
183
- throw error;
184
- }
185
- return merge(config);
201
+ return resolveConfig(path);
186
202
  }
187
203
  function merge(config) {
188
204
  const merged = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tixyel/cli",
3
- "version": "2.0.2",
3
+ "version": "2.2.0",
4
4
  "description": "CLI tool for streamelements widgets",
5
5
  "keywords": [
6
6
  "cli",
@@ -54,18 +54,25 @@
54
54
  "cssnano": "^7.1.2",
55
55
  "esbuild": "^0.25.12",
56
56
  "fast-glob": "^3.3.3",
57
- "html-minifier": "^4.0.0",
58
57
  "html-minifier-terser": "^7.2.0",
59
58
  "inquirer": "^13.0.2",
60
- "javascript-obfuscator": "^4.1.1",
59
+ "javascript-obfuscator": "4.1.1",
61
60
  "jsonc-parser": "^3.3.1",
62
61
  "jszip": "^3.10.1",
63
62
  "postcss": "^8.5.6",
64
- "postcss-nested": "^7.0.2"
63
+ "postcss-nested": "^7.0.2",
64
+ "react": "^19.2.1",
65
+ "react-dom": "^19.2.1"
65
66
  },
66
67
  "devDependencies": {
67
68
  "@types/html-minifier": "^4.0.6",
68
69
  "@types/html-minifier-terser": "^7.0.2",
69
- "@types/inquirer": "^9.0.9"
70
+ "@types/inquirer": "^9.0.9",
71
+ "@types/react": "^19.2.7",
72
+ "@types/react-dom": "^19.2.3"
73
+ },
74
+ "publishConfig": {
75
+ "access": "public",
76
+ "provenance": false
70
77
  }
71
78
  }