@tixyel/cli 1.0.0 → 2.0.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/dev.d.ts +2 -0
- package/dist/dev.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +188 -33
- package/dist/templates/config.d.ts +1 -0
- package/dist/templates/config.js +12 -0
- package/dist/templates/workspace.d.ts +1 -0
- package/dist/templates/workspace.js +111 -0
- package/dist/types/workspace.d.ts +47 -0
- package/dist/types/workspace.js +4 -0
- package/dist/widget.d.ts +31 -0
- package/dist/widget.js +499 -0
- package/dist/workspace.d.ts +111 -0
- package/dist/workspace.js +225 -0
- package/package.json +19 -16
- package/dist/commands/base.d.ts +0 -24
- package/dist/commands/base.js +0 -15
- package/dist/commands/build.d.ts +0 -15
- package/dist/commands/build.js +0 -155
- package/dist/commands/generate-new.d.ts +0 -11
- package/dist/commands/generate-new.js +0 -146
- package/dist/commands/generate.d.ts +0 -11
- package/dist/commands/generate.js +0 -198
- package/dist/commands/init.d.ts +0 -9
- package/dist/commands/init.js +0 -140
- package/dist/types/tixyel-cli-config.d.ts +0 -149
- package/dist/types/tixyel-cli-config.js +0 -162
- package/dist/types/tixyel-config.d.ts +0 -83
- package/dist/types/tixyel-config.js +0 -43
- package/dist/utils/build-processor.d.ts +0 -12
- package/dist/utils/build-processor.js +0 -156
- package/dist/utils/config.d.ts +0 -23
- package/dist/utils/config.js +0 -34
- package/dist/utils/find-widgets.d.ts +0 -19
- package/dist/utils/find-widgets.js +0 -35
- package/dist/utils/load-cli-config.d.ts +0 -5
- package/dist/utils/load-cli-config.js +0 -66
- package/dist/utils/version.d.ts +0 -12
- package/dist/utils/version.js +0 -49
- package/dist/utils/workspace.d.ts +0 -13
- package/dist/utils/workspace.js +0 -43
package/dist/widget.js
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { minify as minifyHTML } from 'html-minifier-terser';
|
|
3
|
+
import JavaScriptObfuscator from 'javascript-obfuscator';
|
|
4
|
+
import { dirname, join, relative, resolve } from 'path';
|
|
5
|
+
import { readFile as readFilePromise } from 'fs/promises';
|
|
6
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
7
|
+
import autoprefixer from 'autoprefixer';
|
|
8
|
+
import { parse } from 'jsonc-parser';
|
|
9
|
+
import nested from 'postcss-nested';
|
|
10
|
+
import FastGlob from 'fast-glob';
|
|
11
|
+
import inquirer from 'inquirer';
|
|
12
|
+
import postcss from 'postcss';
|
|
13
|
+
import cssnano from 'cssnano';
|
|
14
|
+
import JSZip from 'jszip';
|
|
15
|
+
export async function createWidget(path, metadata, config, root) {
|
|
16
|
+
try {
|
|
17
|
+
console.log(`📁 Creating ${metadata?.name} at: ${path}`);
|
|
18
|
+
// Create directory
|
|
19
|
+
await mkdir(path, { recursive: true });
|
|
20
|
+
// Calculate config path if workspace root is provided
|
|
21
|
+
const configPath = root ? getConfigPathFromWidget(path, root) : undefined;
|
|
22
|
+
// Prompt for metadata if not provided
|
|
23
|
+
const widgetMetadata = await getMetadata(metadata);
|
|
24
|
+
// Create .tixyel configuration
|
|
25
|
+
const dotTixyel = {
|
|
26
|
+
name: metadata?.name,
|
|
27
|
+
version: '0.0.0',
|
|
28
|
+
description: widgetMetadata?.description || '',
|
|
29
|
+
config: configPath,
|
|
30
|
+
metadata: {
|
|
31
|
+
...config.metadata,
|
|
32
|
+
...widgetMetadata,
|
|
33
|
+
name: undefined,
|
|
34
|
+
description: undefined,
|
|
35
|
+
},
|
|
36
|
+
dirs: config.dirs || {
|
|
37
|
+
entry: 'development',
|
|
38
|
+
output: 'finished',
|
|
39
|
+
compacted: 'compacted',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
await writeFile(resolve(path, '.tixyel'), JSON.stringify(dotTixyel, null, 2), 'utf-8');
|
|
43
|
+
// Create scaffold files from the workspace config
|
|
44
|
+
const scaffold = config.scaffold || [];
|
|
45
|
+
let created = { files: 0, folders: 0 };
|
|
46
|
+
async function processScaffoldItem(item, basePath) {
|
|
47
|
+
const fullPath = resolve(basePath, item.name);
|
|
48
|
+
if (item.type === 'folder') {
|
|
49
|
+
await mkdir(fullPath, { recursive: true });
|
|
50
|
+
created.folders++;
|
|
51
|
+
// Process folder children if any
|
|
52
|
+
if (item.content && Array.isArray(item.content) && item.content.length) {
|
|
53
|
+
for (const child of item.content) {
|
|
54
|
+
await processScaffoldItem(child, fullPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (item.type === 'file') {
|
|
59
|
+
// Write file
|
|
60
|
+
await writeFile(fullPath, item.content || '', 'utf-8');
|
|
61
|
+
created.files++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const item of scaffold) {
|
|
65
|
+
await processScaffoldItem(item, path);
|
|
66
|
+
}
|
|
67
|
+
console.log(`✅ Widget ${dotTixyel.name} created successfully!`);
|
|
68
|
+
if (dotTixyel.description && dotTixyel.description.length) {
|
|
69
|
+
console.log(` - Description: ${dotTixyel.description}`);
|
|
70
|
+
}
|
|
71
|
+
if (dotTixyel.metadata?.tags && dotTixyel.metadata?.tags.length) {
|
|
72
|
+
console.log(` - Tags: ${dotTixyel.metadata.tags.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
if (dotTixyel.config) {
|
|
75
|
+
console.log(` - Config Path: ${dotTixyel.config}`);
|
|
76
|
+
}
|
|
77
|
+
if (created.folders > 0 || created.files > 0) {
|
|
78
|
+
console.log(` - Scaffolded: ${created.folders} folders, ${created.files} files`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error('❌ Failed to create widget:', error);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function getMetadata(existingMetadata) {
|
|
87
|
+
const answers = await inquirer.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'description',
|
|
91
|
+
message: 'Widget description:',
|
|
92
|
+
default: existingMetadata?.description || '',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'input',
|
|
96
|
+
name: 'tags',
|
|
97
|
+
message: 'Widget tags (comma-separated):',
|
|
98
|
+
default: existingMetadata?.tags ? existingMetadata.tags.join(', ') : '',
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
const tagsArray = answers.tags
|
|
102
|
+
? answers.tags
|
|
103
|
+
.split(',')
|
|
104
|
+
.map((tag) => tag.trim())
|
|
105
|
+
.filter((tag) => tag.length)
|
|
106
|
+
: [];
|
|
107
|
+
return {
|
|
108
|
+
description: answers.description,
|
|
109
|
+
tags: tagsArray,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function getConfigPathFromWidget(widgetPath, workspaceRoot) {
|
|
113
|
+
const configTs = resolve(workspaceRoot, 'tixyel.config.ts');
|
|
114
|
+
const configJs = resolve(workspaceRoot, 'tixyel.config.js');
|
|
115
|
+
const configPathMjs = resolve(workspaceRoot, 'tixyel.config.mjs');
|
|
116
|
+
const targetConfig = existsSync(configTs) ? configTs : existsSync(configJs) ? configJs : existsSync(configPathMjs) ? configPathMjs : null;
|
|
117
|
+
if (!targetConfig) {
|
|
118
|
+
throw new Error('❌ Workspace configuration file not found.');
|
|
119
|
+
}
|
|
120
|
+
const relativePath = relative(widgetPath, targetConfig);
|
|
121
|
+
const normalized = relativePath.replace(/\\/g, '/');
|
|
122
|
+
return normalized.startsWith('.') ? normalized : `./${normalized}`;
|
|
123
|
+
}
|
|
124
|
+
export async function getNextWidgetNumber(parentPath) {
|
|
125
|
+
try {
|
|
126
|
+
if (!existsSync(parentPath)) {
|
|
127
|
+
return '01';
|
|
128
|
+
}
|
|
129
|
+
const entries = readdirSync(parentPath);
|
|
130
|
+
const widgetNumbers = entries
|
|
131
|
+
.filter((name) => /^\d+\s*-\s*/.test(name))
|
|
132
|
+
.map((name) => parseInt(name.split('-')[0], 10))
|
|
133
|
+
.filter((num) => !isNaN(num));
|
|
134
|
+
const maxNum = widgetNumbers.length > 0 ? Math.max(...widgetNumbers) : 0;
|
|
135
|
+
return String(maxNum + 1).padStart(2, '0');
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return '01';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function merge(config) {
|
|
142
|
+
const merged = {
|
|
143
|
+
...config,
|
|
144
|
+
version: config.version || '0.0.0',
|
|
145
|
+
description: config.description || '',
|
|
146
|
+
metadata: {
|
|
147
|
+
...config.metadata,
|
|
148
|
+
},
|
|
149
|
+
dirs: {
|
|
150
|
+
entry: 'development',
|
|
151
|
+
output: 'finished',
|
|
152
|
+
compacted: 'compacted',
|
|
153
|
+
...config.dirs,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
return merged;
|
|
157
|
+
}
|
|
158
|
+
export function validateDotTixyel(config) {
|
|
159
|
+
if (typeof config !== 'object' || !config || config === null || !config.name || typeof config.name !== 'string' || !config.name.trim().length) {
|
|
160
|
+
throw new Error('❌ Invalid .tixyel: "name" is required and must be a non-empty string.');
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
export async function readDotTixyel(path) {
|
|
165
|
+
try {
|
|
166
|
+
const dotTixyelPath = resolve(path, '.tixyel');
|
|
167
|
+
if (!existsSync(dotTixyelPath)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const content = await readFilePromise(dotTixyelPath, 'utf-8');
|
|
171
|
+
const config = parse(content);
|
|
172
|
+
if (!validateDotTixyel(config)) {
|
|
173
|
+
console.error(`Invalid .tixyel configuration in ${path}`);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return merge(config);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export async function findWidgets(root, depth, ignore) {
|
|
183
|
+
// Build glob pattern with depth limit
|
|
184
|
+
const depthPattern = Array.from({ length: depth }, (_, i) => '*'.repeat(i + 1)).join(',');
|
|
185
|
+
const pattern = `{${depthPattern}}/.tixyel`;
|
|
186
|
+
// Build ignore patterns (folders and files)
|
|
187
|
+
const ignorePatterns = ['node_modules', '.git', 'dist', ...ignore];
|
|
188
|
+
// Find all .tixyel files
|
|
189
|
+
const dotTixyels = await FastGlob(pattern, {
|
|
190
|
+
cwd: root,
|
|
191
|
+
absolute: true,
|
|
192
|
+
onlyFiles: true,
|
|
193
|
+
ignore: ignorePatterns,
|
|
194
|
+
});
|
|
195
|
+
const widgets = [];
|
|
196
|
+
for (const dotTixyelPath of dotTixyels) {
|
|
197
|
+
const path = dirname(dotTixyelPath);
|
|
198
|
+
const config = await readDotTixyel(path);
|
|
199
|
+
if (config) {
|
|
200
|
+
widgets.push({
|
|
201
|
+
path,
|
|
202
|
+
relativePath: relative(root, path),
|
|
203
|
+
config,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return widgets;
|
|
208
|
+
}
|
|
209
|
+
export async function build(widget, versionBump, verbose = false, workspaceConfig) {
|
|
210
|
+
console.log(`🔨 Building widget: ${widget.config.name}`);
|
|
211
|
+
if (verbose) {
|
|
212
|
+
console.log(` - Path: ${widget.path}`);
|
|
213
|
+
}
|
|
214
|
+
if (versionBump !== 'none') {
|
|
215
|
+
const newVersion = await bumpVersion(widget.path, versionBump);
|
|
216
|
+
if (newVersion) {
|
|
217
|
+
console.log(` - Bumped version to: ${newVersion}`);
|
|
218
|
+
widget.config.version = newVersion;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (widget.config && workspaceConfig) {
|
|
222
|
+
try {
|
|
223
|
+
await processBuild(widget, workspaceConfig, verbose);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
console.error(`❌ Build failed for widget ${widget.config.name}:`, error);
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
console.warn(`⚠️ Skipping build for widget ${widget.config.name}: Missing configuration.`);
|
|
232
|
+
throw new Error('Missing configuration for build process.');
|
|
233
|
+
}
|
|
234
|
+
console.log(`✅ Build completed for widget: ${widget.config.name}`);
|
|
235
|
+
}
|
|
236
|
+
export async function processBuild(widget, workspaceConfig, verbose = false) {
|
|
237
|
+
const entryDir = join(widget.path, widget.config.dirs?.entry || workspaceConfig.dirs?.entry || 'development');
|
|
238
|
+
const outDir = join(widget.path, widget.config.dirs?.output || workspaceConfig.dirs?.output || 'finished');
|
|
239
|
+
const compactedDir = join(widget.path, widget.config.dirs?.compacted || workspaceConfig.dirs?.compacted || 'widgetIO');
|
|
240
|
+
if (!existsSync(entryDir)) {
|
|
241
|
+
throw new Error(`Entry directory not found: ${entryDir}`);
|
|
242
|
+
}
|
|
243
|
+
mkdirSync(outDir, { recursive: true });
|
|
244
|
+
const findPatterns = workspaceConfig.build?.find || {
|
|
245
|
+
html: ['index.html'],
|
|
246
|
+
script: ['script.js'],
|
|
247
|
+
css: ['styles.css'],
|
|
248
|
+
fields: ['fields.json'],
|
|
249
|
+
};
|
|
250
|
+
const resultMapping = workspaceConfig.build?.result || {
|
|
251
|
+
'HTML.html': 'html',
|
|
252
|
+
'SCRIPT.js': 'script',
|
|
253
|
+
'CSS.css': 'css',
|
|
254
|
+
'FIELDS.json': 'fields',
|
|
255
|
+
};
|
|
256
|
+
const compactedMapping = workspaceConfig.build?.widgetIO || {
|
|
257
|
+
'html.txt': 'html',
|
|
258
|
+
'js.txt': 'script',
|
|
259
|
+
'css.txt': 'css',
|
|
260
|
+
'fields.txt': 'fields',
|
|
261
|
+
};
|
|
262
|
+
// const results: Record<string, string> = {};
|
|
263
|
+
const normalizeList = (value) => (Array.isArray(value) ? value.filter(Boolean) : []);
|
|
264
|
+
const findAndRead = (baseDir, patterns) => {
|
|
265
|
+
const contents = [];
|
|
266
|
+
for (const pattern of patterns) {
|
|
267
|
+
const fullPath = join(baseDir, pattern);
|
|
268
|
+
if (existsSync(fullPath)) {
|
|
269
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
270
|
+
contents.push(content);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return contents;
|
|
274
|
+
};
|
|
275
|
+
// watermark for the files
|
|
276
|
+
var ref = ` ____ _ _ _
|
|
277
|
+
/ __ \\ | |_ (_) __ __ _ _ ___ | |
|
|
278
|
+
/ / _\` | | __| | | \\ \\/ / | | | | / _ \\ | |
|
|
279
|
+
| | (_| | | |_ | | > < | |_| | | __/ | |
|
|
280
|
+
\\ \\__,_| \\__| |_| /_/\\_\\ \\__, | \\___| |_|
|
|
281
|
+
\\____/ |___/\n`;
|
|
282
|
+
var watermark = {
|
|
283
|
+
html: [
|
|
284
|
+
`<!---`,
|
|
285
|
+
ref,
|
|
286
|
+
`Generated by Tixyel Widgets SDK`,
|
|
287
|
+
`Widget name: ${widget.config.name}`,
|
|
288
|
+
`Version: ${widget.config.version}`,
|
|
289
|
+
`Description: ${widget.config.description}`,
|
|
290
|
+
`Tags: ${widget.config.metadata?.tags?.join(',')}`,
|
|
291
|
+
`Made by: ${widget.config.metadata?.author}`,
|
|
292
|
+
widget.config.metadata?.clientId ? `Made for ${widget.config.metadata?.clientId}` : undefined,
|
|
293
|
+
' ',
|
|
294
|
+
'DO NOT EDIT, SHARE OR DISTRIBUTE THIS CODE WITHOUT PERMISSION FROM THE AUTHOR',
|
|
295
|
+
`--->`,
|
|
296
|
+
]
|
|
297
|
+
.filter(Boolean)
|
|
298
|
+
.join('\n'),
|
|
299
|
+
css: [
|
|
300
|
+
`/**`,
|
|
301
|
+
ref,
|
|
302
|
+
`Generated by Tixyel Widgets SDK`,
|
|
303
|
+
`Widget name: ${widget.config.name}`,
|
|
304
|
+
`Version: ${widget.config.version}`,
|
|
305
|
+
`Description: ${widget.config.description}`,
|
|
306
|
+
`Tags: ${widget.config.metadata?.tags?.join(',')}`,
|
|
307
|
+
`Made by: ${widget.config.metadata?.author}`,
|
|
308
|
+
widget.config.metadata?.clientId ? `Made for ${widget.config.metadata?.clientId}` : undefined,
|
|
309
|
+
' ',
|
|
310
|
+
'DO NOT EDIT, SHARE OR DISTRIBUTE THIS CODE WITHOUT PERMISSION FROM THE AUTHOR',
|
|
311
|
+
`*/`,
|
|
312
|
+
]
|
|
313
|
+
.filter(Boolean)
|
|
314
|
+
.join('\n'),
|
|
315
|
+
script: [
|
|
316
|
+
`/**`,
|
|
317
|
+
ref,
|
|
318
|
+
`Generated by Tixyel Widgets SDK`,
|
|
319
|
+
`Widget name: ${widget.config.name}`,
|
|
320
|
+
`Version: ${widget.config.version}`,
|
|
321
|
+
`Description: ${widget.config.description}`,
|
|
322
|
+
`Tags: ${widget.config.metadata?.tags?.join(',')}`,
|
|
323
|
+
`Made by: ${widget.config.metadata?.author}`,
|
|
324
|
+
widget.config.metadata?.clientId ? `Made for ${widget.config.metadata?.clientId}` : undefined,
|
|
325
|
+
' ',
|
|
326
|
+
'DO NOT EDIT, SHARE OR DISTRIBUTE THIS CODE WITHOUT PERMISSION FROM THE AUTHOR',
|
|
327
|
+
`*/`,
|
|
328
|
+
]
|
|
329
|
+
.filter(Boolean)
|
|
330
|
+
.join('\n'),
|
|
331
|
+
};
|
|
332
|
+
const results = Object.fromEntries(await Promise.all(Object.entries(findPatterns).map(async ([key, patterns]) => {
|
|
333
|
+
let result = '';
|
|
334
|
+
const list = normalizeList(patterns);
|
|
335
|
+
if (list.length === 0) {
|
|
336
|
+
return [key, ''];
|
|
337
|
+
}
|
|
338
|
+
// Process HTML
|
|
339
|
+
if (['html', 'HTML'].some((k) => key === k)) {
|
|
340
|
+
result += watermark.html + '\n';
|
|
341
|
+
if (verbose)
|
|
342
|
+
console.log(` - Processing HTML...`);
|
|
343
|
+
const files = findAndRead(entryDir, list);
|
|
344
|
+
let mergedHTML = '';
|
|
345
|
+
for await (const fileContent of files) {
|
|
346
|
+
// Extract body content
|
|
347
|
+
const bodyMatch = fileContent.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
348
|
+
if (bodyMatch && bodyMatch[1]) {
|
|
349
|
+
mergedHTML += bodyMatch[1].trim() + '\n';
|
|
350
|
+
}
|
|
351
|
+
// Extract and inline styles
|
|
352
|
+
const styleMatches = fileContent.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/gi);
|
|
353
|
+
for (const match of styleMatches) {
|
|
354
|
+
mergedHTML += `<style>${match[1]}</style>\n`;
|
|
355
|
+
}
|
|
356
|
+
// Extract and inline <script> tags
|
|
357
|
+
const scriptMatches = fileContent.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/gi);
|
|
358
|
+
for (const match of scriptMatches) {
|
|
359
|
+
if (match[1]) {
|
|
360
|
+
mergedHTML += `<script>${match[1]}</script>\n`;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const minified = await minifyHTML(mergedHTML, workspaceConfig.build?.obfuscation?.html);
|
|
365
|
+
result += minified.trim();
|
|
366
|
+
}
|
|
367
|
+
else if (['css', 'style', 'styles'].some((k) => key === k)) {
|
|
368
|
+
result += watermark.css + '\n';
|
|
369
|
+
if (verbose)
|
|
370
|
+
console.log(` - Processing CSS...`);
|
|
371
|
+
const files = findAndRead(entryDir, list);
|
|
372
|
+
let mergedCSS = '';
|
|
373
|
+
for await (const content of files) {
|
|
374
|
+
const plugin = [
|
|
375
|
+
autoprefixer({
|
|
376
|
+
overrideBrowserslist: ['Chrome 127'],
|
|
377
|
+
...workspaceConfig.build?.obfuscation?.css?.autoprefixer,
|
|
378
|
+
}),
|
|
379
|
+
cssnano(workspaceConfig.build?.obfuscation?.css?.cssnano),
|
|
380
|
+
];
|
|
381
|
+
if (workspaceConfig.build?.obfuscation?.css?.removeNesting) {
|
|
382
|
+
plugin.unshift(nested());
|
|
383
|
+
}
|
|
384
|
+
const processed = await postcss(plugin).process(content, { from: undefined });
|
|
385
|
+
mergedCSS += processed.css + '\n';
|
|
386
|
+
}
|
|
387
|
+
result += mergedCSS.trim();
|
|
388
|
+
}
|
|
389
|
+
else if (['script', 'js', 'javascript'].some((k) => key === k)) {
|
|
390
|
+
result += watermark.script + '\n';
|
|
391
|
+
if (verbose)
|
|
392
|
+
console.log(` - Processing JavaScript...`);
|
|
393
|
+
const files = findAndRead(entryDir, list);
|
|
394
|
+
let mergedJS = '';
|
|
395
|
+
for await (const content of files) {
|
|
396
|
+
const obfuscated = JavaScriptObfuscator.obfuscate(content, workspaceConfig.build?.obfuscation?.javascript);
|
|
397
|
+
mergedJS += obfuscated.getObfuscatedCode() + '\n';
|
|
398
|
+
}
|
|
399
|
+
result += mergedJS.trim();
|
|
400
|
+
}
|
|
401
|
+
else if (['fields', 'FIELDS', 'fielddata', 'fieldData'].some((k) => key === k)) {
|
|
402
|
+
if (verbose)
|
|
403
|
+
console.log(` - Processing Json...`);
|
|
404
|
+
const files = findAndRead(entryDir, list);
|
|
405
|
+
let mergedFields = {};
|
|
406
|
+
for await (const content of files) {
|
|
407
|
+
try {
|
|
408
|
+
const noComments = parse(content);
|
|
409
|
+
const parsed = JSON.parse(JSON.stringify(noComments));
|
|
410
|
+
mergedFields = {
|
|
411
|
+
...mergedFields,
|
|
412
|
+
...parsed,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
console.warn(` ⚠️ Failed to parse fields JSON: ${error}`);
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
result += JSON.stringify(mergedFields, null, 2);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
if (verbose)
|
|
424
|
+
console.log(` - Unknown build key: ${key}, the available keys are html, css, script and fields.`);
|
|
425
|
+
result += '';
|
|
426
|
+
}
|
|
427
|
+
return [key, result];
|
|
428
|
+
})));
|
|
429
|
+
for await (const [filename, key] of Object.entries(resultMapping)) {
|
|
430
|
+
const content = results[key];
|
|
431
|
+
if (content) {
|
|
432
|
+
const outPath = join(outDir, filename);
|
|
433
|
+
writeFileSync(outPath, content, 'utf-8');
|
|
434
|
+
if (verbose)
|
|
435
|
+
console.log(` ✓ Written: ${outPath}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// widgetIO zip
|
|
439
|
+
try {
|
|
440
|
+
const zip = new JSZip();
|
|
441
|
+
for await (const [filename, key] of Object.entries(compactedMapping)) {
|
|
442
|
+
const content = results[key];
|
|
443
|
+
if (content) {
|
|
444
|
+
zip.file(filename, content);
|
|
445
|
+
if (verbose)
|
|
446
|
+
console.log(` ✓ Added to ZIP: ${filename}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
zip.file('widget.ini', `[HTML]\npath = "html.txt"\n\n[CSS]\npath = "css.txt"\n\n[JS]\npath = "js.txt"\n\n[FIELDS]\npath = "fields.txt"\n\n[DATA]\npath = "data.txt"`);
|
|
450
|
+
// check if data.txt exists in results, otherwise create empty
|
|
451
|
+
const dataContent = results['data'] || '{}';
|
|
452
|
+
zip.file('data.txt', dataContent);
|
|
453
|
+
const result = await zip
|
|
454
|
+
.generateInternalStream({ type: 'base64' })
|
|
455
|
+
.accumulate()
|
|
456
|
+
.then((data) => data);
|
|
457
|
+
const zipPath = join(compactedDir + '/' + widget.config.version || '0.0.0', `${widget.config.name}.zip`);
|
|
458
|
+
mkdirSync(compactedDir + '/' + widget.config.version, { recursive: true });
|
|
459
|
+
writeFileSync(zipPath, result, 'base64');
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
console.error(`❌ Failed to create widgetIO ZIP for widget ${widget.config.name}:`, error);
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
export async function bumpVersion(widgetPath, bumpType = 'patch') {
|
|
467
|
+
try {
|
|
468
|
+
const configPath = join(widgetPath, '.tixyel');
|
|
469
|
+
const content = await readFilePromise(configPath, 'utf-8');
|
|
470
|
+
const config = parse(content);
|
|
471
|
+
if (!config.version) {
|
|
472
|
+
config.version = '0.0.0';
|
|
473
|
+
}
|
|
474
|
+
const [major, minor, patch] = config.version.split('.').map(Number);
|
|
475
|
+
let newVersion;
|
|
476
|
+
switch (bumpType) {
|
|
477
|
+
case 'major': {
|
|
478
|
+
newVersion = `${major + 1}.0.0`;
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
case 'minor': {
|
|
482
|
+
newVersion = `${major}.${minor + 1}.0`;
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
case 'patch': {
|
|
486
|
+
newVersion = `${major}.${minor}.${patch + 1}`;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
config.version = newVersion;
|
|
491
|
+
const formattedContent = JSON.stringify(config, null, 2);
|
|
492
|
+
await writeFile(configPath, formattedContent, 'utf-8');
|
|
493
|
+
return newVersion;
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
console.error(`Failed to bump version in ${widgetPath}:`, error);
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Options as HtmlMinifierOptions } from 'html-minifier-terser';
|
|
2
|
+
import type { ObfuscatorOptions } from 'javascript-obfuscator';
|
|
3
|
+
import type autoprefixer from 'autoprefixer';
|
|
4
|
+
import type cssnanoPlugin from 'cssnano';
|
|
5
|
+
export type ScaffoldItem = ScaffoldFile | ScaffoldFolder;
|
|
6
|
+
export interface ScaffoldFile {
|
|
7
|
+
name: string;
|
|
8
|
+
type: 'file';
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ScaffoldFolder {
|
|
12
|
+
name: string;
|
|
13
|
+
type: 'folder';
|
|
14
|
+
content?: ScaffoldItem[];
|
|
15
|
+
}
|
|
16
|
+
export interface WorkspaceConfig<Find extends BuildFindMap = BuildFindMap> {
|
|
17
|
+
/**
|
|
18
|
+
* Search options for locating widget files
|
|
19
|
+
*/
|
|
20
|
+
search?: {
|
|
21
|
+
/**
|
|
22
|
+
* Maximum directory depth to search
|
|
23
|
+
*/
|
|
24
|
+
maxDepth?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Folders and files to ignore during search
|
|
27
|
+
*/
|
|
28
|
+
ignore?: string[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Metadata applied to all widgets in the workspace
|
|
32
|
+
*/
|
|
33
|
+
metadata?: {
|
|
34
|
+
name?: string;
|
|
35
|
+
author?: string;
|
|
36
|
+
clientId?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
tags?: string[];
|
|
39
|
+
};
|
|
40
|
+
dirs?: {
|
|
41
|
+
entry?: string;
|
|
42
|
+
output?: string;
|
|
43
|
+
compacted?: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Scaffold structure to create when generating a new widget
|
|
47
|
+
*/
|
|
48
|
+
scaffold?: ScaffoldItem[];
|
|
49
|
+
/**
|
|
50
|
+
* Build configuration
|
|
51
|
+
*/
|
|
52
|
+
build?: {
|
|
53
|
+
/**
|
|
54
|
+
* Run builds in parallel
|
|
55
|
+
*/
|
|
56
|
+
parallel?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Verbose output during build
|
|
59
|
+
*/
|
|
60
|
+
verbose?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* File patterns to locate widget files
|
|
63
|
+
*/
|
|
64
|
+
find?: Find;
|
|
65
|
+
/**
|
|
66
|
+
* Mapping of output files to find keys
|
|
67
|
+
*/
|
|
68
|
+
result?: BuildResultMap<Find>;
|
|
69
|
+
/**
|
|
70
|
+
* Mapping of widgetIO output files to find keys
|
|
71
|
+
*/
|
|
72
|
+
widgetIO?: BuildResultMap<Find>;
|
|
73
|
+
/**
|
|
74
|
+
* Obfuscation and minification settings
|
|
75
|
+
*/
|
|
76
|
+
obfuscation?: {
|
|
77
|
+
/**
|
|
78
|
+
* JavaScript obfuscation options
|
|
79
|
+
*/
|
|
80
|
+
javascript?: ObfuscatorOptions;
|
|
81
|
+
/**
|
|
82
|
+
* CSS minification options
|
|
83
|
+
*/
|
|
84
|
+
css?: {
|
|
85
|
+
/**
|
|
86
|
+
* Remove nesting rules
|
|
87
|
+
*/
|
|
88
|
+
removeNesting?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Autoprefixer options
|
|
91
|
+
*/
|
|
92
|
+
autoprefixer?: autoprefixer.Options;
|
|
93
|
+
/**
|
|
94
|
+
* cssnano options
|
|
95
|
+
*/
|
|
96
|
+
cssnano?: cssnanoPlugin.Options;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* HTML minification options
|
|
100
|
+
*/
|
|
101
|
+
html?: HtmlMinifierOptions;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
type BuildFindMap = Record<string, string[]>;
|
|
106
|
+
type BuildResultMap<Find extends BuildFindMap> = Record<string, keyof Find>;
|
|
107
|
+
export declare function defineWorkspaceConfig<const Find extends BuildFindMap>(config: WorkspaceConfig<Find>): WorkspaceConfig<Find>;
|
|
108
|
+
export declare function findWorkspaceRoot(startPath?: string): Promise<string | null>;
|
|
109
|
+
export declare function validateWorkspace(): Promise<string>;
|
|
110
|
+
export declare function loadWorkspace(path: string): Promise<WorkspaceConfig<BuildFindMap>>;
|
|
111
|
+
export {};
|