@fgv/ts-res-cli 5.0.0-2 → 5.0.0-21

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/README.md CHANGED
@@ -33,6 +33,13 @@ ts-res-compile compile \
33
33
  -o ./dist/resources.ts \
34
34
  --format ts \
35
35
  --include-metadata
36
+
37
+ # Create a complete bundle with metadata and configuration
38
+ ts-res-compile compile \
39
+ -i ./resources \
40
+ -o ./dist/resources.bundle.json \
41
+ --format bundle \
42
+ --include-metadata
36
43
  ```
37
44
 
38
45
  ### Validate Resources
@@ -66,7 +73,7 @@ Compiles resources from input to output format.
66
73
 
67
74
  **Optional Options:**
68
75
  - `-c, --context <json>` - Context filter for resources (JSON string)
69
- - `-f, --format <format>` - Output format (json, js, ts, binary) [default: json]
76
+ - `-f, --format <format>` - Output format (compiled, source, js, ts, binary, bundle) [default: compiled]
70
77
  - `--debug` - Include debug information [default: false]
71
78
  - `-v, --verbose` - Verbose output [default: false]
72
79
  - `-q, --quiet` - Quiet output [default: false]
@@ -134,7 +141,8 @@ The CLI supports the following input formats:
134
141
 
135
142
  ## Output Formats
136
143
 
137
- ### JSON (default)
144
+ ### Compiled (default)
145
+ Compiled resource collection optimized for runtime use:
138
146
  ```json
139
147
  {
140
148
  "resources": {
@@ -145,6 +153,46 @@ The CLI supports the following input formats:
145
153
  }
146
154
  ```
147
155
 
156
+ ### Source
157
+ Original resource collection format:
158
+ ```json
159
+ {
160
+ "resources": [
161
+ {
162
+ "id": "welcome.message",
163
+ "resourceTypeName": "json",
164
+ "candidates": [
165
+ {
166
+ "json": { "text": "Welcome!" },
167
+ "conditions": { "language": "en" }
168
+ }
169
+ ]
170
+ }
171
+ ]
172
+ }
173
+ ```
174
+
175
+ ### Bundle
176
+ Complete resource bundle with metadata, configuration, and compiled resources:
177
+ ```json
178
+ {
179
+ "metadata": {
180
+ "dateBuilt": "2025-01-01T00:00:00.000Z",
181
+ "checksum": "abc12345",
182
+ "version": "1.0.0",
183
+ "description": "Generated bundle from ts-res-cli"
184
+ },
185
+ "config": {
186
+ "qualifierTypes": [...],
187
+ "qualifiers": [...],
188
+ "resourceTypes": [...]
189
+ },
190
+ "compiledCollection": {
191
+ "resources": {...}
192
+ }
193
+ }
194
+ ```
195
+
148
196
  ### JavaScript
149
197
  ```javascript
150
198
  module.exports = {
package/lib/cli.d.ts CHANGED
@@ -28,6 +28,10 @@ export declare class TsResCliApp {
28
28
  * Handles the config command
29
29
  */
30
30
  private _handleConfigCommand;
31
+ /**
32
+ * Handles the archive command
33
+ */
34
+ private _handleArchiveCommand;
31
35
  /**
32
36
  * Parses and validates compile options
33
37
  */
package/lib/cli.js CHANGED
@@ -90,7 +90,7 @@ class TsResCliApp {
90
90
  .option('-c, --context <json>', 'Context filter for resources (JSON string)')
91
91
  .option('--context-filter <token>', 'Context filter token (pipe-separated, e.g., "language=en-US|territory=US")')
92
92
  .option('--qualifier-defaults <token>', 'Qualifier default values token (pipe-separated, e.g., "language=en-US,en-CA|territory=US")')
93
- .option('-f, --format <format>', 'Output format', 'compiled')
93
+ .option('-f, --format <format>', 'Output format (compiled, source, js, ts, binary, bundle)', 'compiled')
94
94
  .option('--debug', 'Include debug information', false)
95
95
  .option('-v, --verbose', 'Verbose output', false)
96
96
  .option('-q, --quiet', 'Quiet output', false)
@@ -142,6 +142,17 @@ class TsResCliApp {
142
142
  .action(async (options) => {
143
143
  await this._handleConfigCommand(options);
144
144
  });
145
+ this._program
146
+ .command('archive')
147
+ .description('Create ZIP archive of resources and configuration')
148
+ .option('-i, --input <path>', 'Input file or directory path')
149
+ .option('--config <path>', 'Configuration file path')
150
+ .requiredOption('-o, --output <path>', 'Output ZIP file path')
151
+ .option('-v, --verbose', 'Verbose output', false)
152
+ .option('-q, --quiet', 'Quiet output', false)
153
+ .action(async (options) => {
154
+ await this._handleArchiveCommand(options);
155
+ });
145
156
  }
146
157
  /**
147
158
  * Handles the compile command
@@ -413,13 +424,68 @@ class TsResCliApp {
413
424
  process.exit(1);
414
425
  }
415
426
  }
427
+ /**
428
+ * Handles the archive command
429
+ */
430
+ async _handleArchiveCommand(options) {
431
+ try {
432
+ // Import ZipArchive dynamically to avoid loading if not needed
433
+ const { ZipArchive } = await Promise.resolve().then(() => __importStar(require('@fgv/ts-res')));
434
+ if (!options.input && !options.config) {
435
+ console.error('Error: At least one of --input or --config must be specified');
436
+ process.exit(1);
437
+ }
438
+ if (!options.quiet) {
439
+ console.log('Creating ZIP archive...');
440
+ if (options.input)
441
+ console.log(`Input: ${options.input}`);
442
+ if (options.config)
443
+ console.log(`Config: ${options.config}`);
444
+ console.log(`Output: ${options.output}`);
445
+ }
446
+ // Create ZIP archive using the new zip-archive packlet
447
+ const creator = new ZipArchive.ZipArchiveCreator();
448
+ const createResult = await creator.createFromBuffer({
449
+ inputPath: options.input,
450
+ configPath: options.config
451
+ }, options.verbose
452
+ ? (phase, progress, message) => {
453
+ console.log(`[${phase}] ${progress}% - ${message}`);
454
+ }
455
+ : undefined);
456
+ if (createResult.isFailure()) {
457
+ console.error(`Error: Failed to create ZIP archive: ${createResult.message}`);
458
+ process.exit(1);
459
+ }
460
+ const { zipBuffer, manifest, size } = createResult.value;
461
+ // Write the ZIP buffer to the specified output file
462
+ const fs = await Promise.resolve().then(() => __importStar(require('fs'))).then((m) => m.promises);
463
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
464
+ const outputPath = path.resolve(options.output);
465
+ const outputDir = path.dirname(outputPath);
466
+ // Ensure output directory exists
467
+ await fs.mkdir(outputDir, { recursive: true });
468
+ // Write ZIP file
469
+ await fs.writeFile(outputPath, zipBuffer);
470
+ if (!options.quiet) {
471
+ console.log(`ZIP archive created successfully: ${outputPath}`);
472
+ console.log(`Archive size: ${(size / 1024).toFixed(2)} KB`);
473
+ console.log(`Manifest:`);
474
+ console.log(JSON.stringify(manifest, null, 2));
475
+ }
476
+ }
477
+ catch (error) {
478
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
479
+ process.exit(1);
480
+ }
481
+ }
416
482
  /**
417
483
  * Parses and validates compile options
418
484
  */
419
485
  _parseCompileOptions(options) {
420
486
  try {
421
487
  const format = options.format;
422
- if (!['compiled', 'source', 'js', 'ts', 'binary'].includes(format)) {
488
+ if (!['compiled', 'source', 'js', 'ts', 'binary', 'bundle'].includes(format)) {
423
489
  return (0, ts_utils_1.fail)(`Invalid format: ${format}`);
424
490
  }
425
491
  // Convert JSON context to contextFilter if provided
package/lib/compiler.d.ts CHANGED
@@ -25,6 +25,7 @@ export interface IFilteredManager {
25
25
  export interface IResourceBlob {
26
26
  resources?: TsRes.ResourceJson.Json.IResourceCollectionDecl;
27
27
  compiledCollection?: TsRes.ResourceJson.Compiled.ICompiledResourceCollection;
28
+ bundle?: TsRes.Bundle.IBundle;
28
29
  metadata?: IResourceInfo;
29
30
  }
30
31
  /**
@@ -74,6 +75,10 @@ export declare class ResourceCompiler {
74
75
  * Generates a source format blob (legacy format)
75
76
  */
76
77
  private _generateSourceBlob;
78
+ /**
79
+ * Generates a bundle blob
80
+ */
81
+ private _generateBundleBlob;
77
82
  /**
78
83
  * Generates resource information
79
84
  */
package/lib/compiler.js CHANGED
@@ -319,6 +319,9 @@ class ResourceCompiler {
319
319
  if (this._options.format === 'compiled') {
320
320
  return this._generateCompiledBlob(filtered);
321
321
  }
322
+ else if (this._options.format === 'bundle') {
323
+ return this._generateBundleBlob(filtered);
324
+ }
322
325
  else {
323
326
  return this._generateSourceBlob(filtered);
324
327
  }
@@ -354,6 +357,32 @@ class ResourceCompiler {
354
357
  return (0, ts_utils_1.succeed)(blob);
355
358
  });
356
359
  }
360
+ /**
361
+ * Generates a bundle blob
362
+ */
363
+ _generateBundleBlob(filtered) {
364
+ if (!this._systemConfiguration) {
365
+ return (0, ts_utils_1.fail)('System configuration is required for bundle generation');
366
+ }
367
+ // Create SystemConfiguration from the loaded configuration
368
+ return TsRes.Config.SystemConfiguration.create(this._systemConfiguration).onSuccess((systemConfig) => {
369
+ // Create bundle using BundleBuilder
370
+ const bundleParams = {
371
+ version: '1.0.0',
372
+ description: 'Generated bundle from ts-res-cli',
373
+ normalize: true // Use order-resilient normalization for consistent output
374
+ };
375
+ return TsRes.Bundle.BundleBuilder.create(filtered.manager, systemConfig, bundleParams).onSuccess((bundle) => {
376
+ const blob = {
377
+ bundle
378
+ };
379
+ if (this._options.includeMetadata) {
380
+ blob.metadata = this._generateResourceInfo(filtered);
381
+ }
382
+ return (0, ts_utils_1.succeed)(blob);
383
+ });
384
+ });
385
+ }
357
386
  /**
358
387
  * Generates resource information
359
388
  */
@@ -400,6 +429,11 @@ class ResourceCompiler {
400
429
  const binaryData = blob.compiledCollection || blob;
401
430
  content = JSON.stringify(binaryData);
402
431
  break;
432
+ case 'bundle':
433
+ // Bundle format outputs the complete bundle with metadata, config, and compiled resources
434
+ const bundleData = this._options.includeMetadata ? blob : blob.bundle;
435
+ content = JSON.stringify(bundleData, null, 2);
436
+ break;
403
437
  default:
404
438
  return (0, ts_utils_1.fail)(`Unsupported output format: ${this._options.format}`);
405
439
  }
package/lib/options.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Output format options for resource compilation
3
3
  */
4
- export type OutputFormat = 'compiled' | 'source' | 'js' | 'ts' | 'binary';
4
+ export type OutputFormat = 'compiled' | 'source' | 'js' | 'ts' | 'binary' | 'bundle';
5
5
  /**
6
6
  * Options for resource compilation
7
7
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-res-cli",
3
- "version": "5.0.0-2",
3
+ "version": "5.0.0-21",
4
4
  "description": "Command-line interface for ts-res resource compilation and management",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -18,15 +18,15 @@
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
20
  "commander": "^11.0.0",
21
- "@fgv/ts-res": "5.0.0-2",
22
- "@fgv/ts-utils": "5.0.0-2",
23
- "@fgv/ts-json": "5.0.0-2",
24
- "@fgv/ts-extras": "5.0.0-2",
25
- "@fgv/ts-json-base": "5.0.0-2"
21
+ "@fgv/ts-res": "5.0.0-21",
22
+ "@fgv/ts-utils": "5.0.0-21",
23
+ "@fgv/ts-json-base": "5.0.0-21",
24
+ "@fgv/ts-extras": "5.0.0-21",
25
+ "@fgv/ts-json": "5.0.0-21"
26
26
  },
27
27
  "devDependencies": {
28
- "@rushstack/heft": "~0.74.0",
29
- "@rushstack/heft-node-rig": "~2.9.0",
28
+ "@rushstack/heft": "~0.74.2",
29
+ "@rushstack/heft-node-rig": "~2.9.3",
30
30
  "@rushstack/eslint-config": "~4.4.0",
31
31
  "@types/heft-jest": "1.0.6",
32
32
  "@types/jest": "^29.5.14",
@@ -35,9 +35,9 @@
35
35
  "@typescript-eslint/parser": "^7.14.1",
36
36
  "eslint": "^8.57.0",
37
37
  "jest": "^29.7.0",
38
- "ts-jest": "^29.4.0",
39
- "typescript": "^5.7.3",
40
- "@fgv/ts-utils-jest": "5.0.0-2"
38
+ "ts-jest": "^29.4.1",
39
+ "typescript": "^5.8.3",
40
+ "@fgv/ts-utils-jest": "5.0.0-21"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "typescript": "^5.7.3"
package/src/cli.ts CHANGED
@@ -86,6 +86,17 @@ interface IConfigCommandOptions {
86
86
  list?: boolean;
87
87
  }
88
88
 
89
+ /**
90
+ * Command-line options for the archive command
91
+ */
92
+ interface IArchiveCommandOptions {
93
+ input?: string;
94
+ config?: string;
95
+ output: string;
96
+ verbose?: boolean;
97
+ quiet?: boolean;
98
+ }
99
+
89
100
  import { ResourceCompiler } from './compiler';
90
101
 
91
102
  /**
@@ -133,7 +144,7 @@ export class TsResCliApp {
133
144
  '--qualifier-defaults <token>',
134
145
  'Qualifier default values token (pipe-separated, e.g., "language=en-US,en-CA|territory=US")'
135
146
  )
136
- .option('-f, --format <format>', 'Output format', 'compiled')
147
+ .option('-f, --format <format>', 'Output format (compiled, source, js, ts, binary, bundle)', 'compiled')
137
148
  .option('--debug', 'Include debug information', false)
138
149
  .option('-v, --verbose', 'Verbose output', false)
139
150
  .option('-q, --quiet', 'Quiet output', false)
@@ -221,6 +232,18 @@ export class TsResCliApp {
221
232
  .action(async (options) => {
222
233
  await this._handleConfigCommand(options);
223
234
  });
235
+
236
+ this._program
237
+ .command('archive')
238
+ .description('Create ZIP archive of resources and configuration')
239
+ .option('-i, --input <path>', 'Input file or directory path')
240
+ .option('--config <path>', 'Configuration file path')
241
+ .requiredOption('-o, --output <path>', 'Output ZIP file path')
242
+ .option('-v, --verbose', 'Verbose output', false)
243
+ .option('-q, --quiet', 'Quiet output', false)
244
+ .action(async (options) => {
245
+ await this._handleArchiveCommand(options);
246
+ });
224
247
  }
225
248
 
226
249
  /**
@@ -557,13 +580,78 @@ export class TsResCliApp {
557
580
  }
558
581
  }
559
582
 
583
+ /**
584
+ * Handles the archive command
585
+ */
586
+ private async _handleArchiveCommand(options: IArchiveCommandOptions): Promise<void> {
587
+ try {
588
+ // Import ZipArchive dynamically to avoid loading if not needed
589
+ const { ZipArchive } = await import('@fgv/ts-res');
590
+
591
+ if (!options.input && !options.config) {
592
+ console.error('Error: At least one of --input or --config must be specified');
593
+ process.exit(1);
594
+ }
595
+
596
+ if (!options.quiet) {
597
+ console.log('Creating ZIP archive...');
598
+ if (options.input) console.log(`Input: ${options.input}`);
599
+ if (options.config) console.log(`Config: ${options.config}`);
600
+ console.log(`Output: ${options.output}`);
601
+ }
602
+
603
+ // Create ZIP archive using the new zip-archive packlet
604
+ const creator = new ZipArchive.ZipArchiveCreator();
605
+ const createResult = await creator.createFromBuffer(
606
+ {
607
+ inputPath: options.input,
608
+ configPath: options.config
609
+ },
610
+ options.verbose
611
+ ? (phase, progress, message) => {
612
+ console.log(`[${phase}] ${progress}% - ${message}`);
613
+ }
614
+ : undefined
615
+ );
616
+
617
+ if (createResult.isFailure()) {
618
+ console.error(`Error: Failed to create ZIP archive: ${createResult.message}`);
619
+ process.exit(1);
620
+ }
621
+
622
+ const { zipBuffer, manifest, size } = createResult.value;
623
+
624
+ // Write the ZIP buffer to the specified output file
625
+ const fs = await import('fs').then((m) => m.promises);
626
+ const path = await import('path');
627
+ const outputPath = path.resolve(options.output);
628
+ const outputDir = path.dirname(outputPath);
629
+
630
+ // Ensure output directory exists
631
+ await fs.mkdir(outputDir, { recursive: true });
632
+
633
+ // Write ZIP file
634
+ await fs.writeFile(outputPath, zipBuffer);
635
+
636
+ if (!options.quiet) {
637
+ console.log(`ZIP archive created successfully: ${outputPath}`);
638
+ console.log(`Archive size: ${(size / 1024).toFixed(2)} KB`);
639
+ console.log(`Manifest:`);
640
+ console.log(JSON.stringify(manifest, null, 2));
641
+ }
642
+ } catch (error) {
643
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
644
+ process.exit(1);
645
+ }
646
+ }
647
+
560
648
  /**
561
649
  * Parses and validates compile options
562
650
  */
563
651
  private _parseCompileOptions(options: ICompileCommandOptions): Result<ICompileOptions> {
564
652
  try {
565
653
  const format = options.format as OutputFormat;
566
- if (!['compiled', 'source', 'js', 'ts', 'binary'].includes(format)) {
654
+ if (!['compiled', 'source', 'js', 'ts', 'binary', 'bundle'].includes(format)) {
567
655
  return fail(`Invalid format: ${format}`);
568
656
  }
569
657
 
package/src/compiler.ts CHANGED
@@ -53,6 +53,7 @@ export interface IFilteredManager {
53
53
  export interface IResourceBlob {
54
54
  resources?: TsRes.ResourceJson.Json.IResourceCollectionDecl;
55
55
  compiledCollection?: TsRes.ResourceJson.Compiled.ICompiledResourceCollection;
56
+ bundle?: TsRes.Bundle.IBundle;
56
57
  metadata?: IResourceInfo;
57
58
  }
58
59
 
@@ -387,6 +388,8 @@ export class ResourceCompiler {
387
388
  try {
388
389
  if (this._options.format === 'compiled') {
389
390
  return this._generateCompiledBlob(filtered);
391
+ } else if (this._options.format === 'bundle') {
392
+ return this._generateBundleBlob(filtered);
390
393
  } else {
391
394
  return this._generateSourceBlob(filtered);
392
395
  }
@@ -427,6 +430,39 @@ export class ResourceCompiler {
427
430
  });
428
431
  }
429
432
 
433
+ /**
434
+ * Generates a bundle blob
435
+ */
436
+ private _generateBundleBlob(filtered: IFilteredManager): Result<IResourceBlob> {
437
+ if (!this._systemConfiguration) {
438
+ return fail('System configuration is required for bundle generation');
439
+ }
440
+
441
+ // Create SystemConfiguration from the loaded configuration
442
+ return TsRes.Config.SystemConfiguration.create(this._systemConfiguration).onSuccess((systemConfig) => {
443
+ // Create bundle using BundleBuilder
444
+ const bundleParams: TsRes.Bundle.IBundleCreateParams = {
445
+ version: '1.0.0',
446
+ description: 'Generated bundle from ts-res-cli',
447
+ normalize: true // Use order-resilient normalization for consistent output
448
+ };
449
+
450
+ return TsRes.Bundle.BundleBuilder.create(filtered.manager, systemConfig, bundleParams).onSuccess(
451
+ (bundle) => {
452
+ const blob: IResourceBlob = {
453
+ bundle
454
+ };
455
+
456
+ if (this._options.includeMetadata) {
457
+ blob.metadata = this._generateResourceInfo(filtered);
458
+ }
459
+
460
+ return succeed(blob);
461
+ }
462
+ );
463
+ });
464
+ }
465
+
430
466
  /**
431
467
  * Generates resource information
432
468
  */
@@ -491,6 +527,11 @@ export class ResourceCompiler {
491
527
  const binaryData = blob.compiledCollection || blob;
492
528
  content = JSON.stringify(binaryData);
493
529
  break;
530
+ case 'bundle':
531
+ // Bundle format outputs the complete bundle with metadata, config, and compiled resources
532
+ const bundleData = this._options.includeMetadata ? blob : blob.bundle;
533
+ content = JSON.stringify(bundleData, null, 2);
534
+ break;
494
535
  default:
495
536
  return fail(`Unsupported output format: ${this._options.format}`);
496
537
  }
package/src/options.ts CHANGED
@@ -23,7 +23,7 @@
23
23
  /**
24
24
  * Output format options for resource compilation
25
25
  */
26
- export type OutputFormat = 'compiled' | 'source' | 'js' | 'ts' | 'binary';
26
+ export type OutputFormat = 'compiled' | 'source' | 'js' | 'ts' | 'binary' | 'bundle';
27
27
 
28
28
  /**
29
29
  * Options for resource compilation
package/.eslintrc.js DELETED
@@ -1,9 +0,0 @@
1
- // This is a workaround for https://github.com/eslint/eslint/issues/3458
2
- require('@rushstack/eslint-config/patch/modern-module-resolution');
3
-
4
- module.exports = {
5
- extends: ['@rushstack/eslint-config/profile/node'],
6
- parserOptions: {
7
- tsconfigRootDir: __dirname
8
- }
9
- };
@@ -1,6 +0,0 @@
1
- {"kind":"O","text":"Invoking: heft build --clean \n"}
2
- {"kind":"O","text":" ---- build started ---- \n"}
3
- {"kind":"O","text":"[build:typescript] Using TypeScript version 5.8.3\n"}
4
- {"kind":"O","text":"[build:lint] Using ESLint version 8.57.0\n"}
5
- {"kind":"O","text":" ---- build finished (7.653s) ---- \n"}
6
- {"kind":"O","text":"-------------------- Finished (7.657s) --------------------\n"}
@@ -1,6 +0,0 @@
1
- {"kind":"O","text":"Invoking: heft build --clean \n"}
2
- {"kind":"O","text":" ---- build started ---- \n"}
3
- {"kind":"O","text":"[build:typescript] Using TypeScript version 5.8.3\n"}
4
- {"kind":"O","text":"[build:lint] Using ESLint version 8.57.0\n"}
5
- {"kind":"O","text":" ---- build finished (7.653s) ---- \n"}
6
- {"kind":"O","text":"-------------------- Finished (7.657s) --------------------\n"}
@@ -1,3 +0,0 @@
1
- {
2
- "nonCachedDurationMs": 8165.2772600000035
3
- }