@fgv/ts-res 5.0.0-16 → 5.0.0-18

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 (32) hide show
  1. package/dist/ts-res.d.ts +461 -0
  2. package/lib/index.d.ts +2 -1
  3. package/lib/index.js +3 -1
  4. package/lib/packlets/zip-archive/convert.d.ts +52 -0
  5. package/lib/packlets/zip-archive/convert.js +104 -0
  6. package/lib/packlets/zip-archive/index.d.ts +23 -0
  7. package/lib/packlets/zip-archive/index.js +95 -0
  8. package/lib/packlets/zip-archive/json.d.ts +64 -0
  9. package/lib/packlets/zip-archive/json.js +24 -0
  10. package/lib/packlets/zip-archive/types.d.ts +98 -0
  11. package/lib/packlets/zip-archive/types.js +39 -0
  12. package/lib/packlets/zip-archive/zipArchiveCreator.d.ts +35 -0
  13. package/lib/packlets/zip-archive/zipArchiveCreator.js +194 -0
  14. package/lib/packlets/zip-archive/zipArchiveFormat.d.ts +70 -0
  15. package/lib/packlets/zip-archive/zipArchiveFormat.js +184 -0
  16. package/lib/packlets/zip-archive/zipArchiveLoader.d.ts +70 -0
  17. package/lib/packlets/zip-archive/zipArchiveLoader.js +285 -0
  18. package/lib/test/unit/zip-archive/convert.test.d.ts +2 -0
  19. package/lib/test/unit/zip-archive/json.test.d.ts +2 -0
  20. package/lib/test/unit/zip-archive/zipArchiveCreator.test.d.ts +2 -0
  21. package/lib/test/unit/zip-archive/zipArchiveFormat.test.d.ts +2 -0
  22. package/lib/test/unit/zip-archive/zipArchiveIdempotency.test.d.ts +2 -0
  23. package/lib/test/unit/zip-archive/zipArchiveLoader.test.d.ts +2 -0
  24. package/package.json +8 -7
  25. package/src/index.ts +3 -1
  26. package/src/packlets/zip-archive/convert.ts +121 -0
  27. package/src/packlets/zip-archive/index.ts +76 -0
  28. package/src/packlets/zip-archive/json.ts +91 -0
  29. package/src/packlets/zip-archive/types.ts +140 -0
  30. package/src/packlets/zip-archive/zipArchiveCreator.ts +229 -0
  31. package/src/packlets/zip-archive/zipArchiveFormat.ts +158 -0
  32. package/src/packlets/zip-archive/zipArchiveLoader.ts +370 -0
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2025 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.ZipArchiveLoader = void 0;
25
+ const ts_utils_1 = require("@fgv/ts-utils");
26
+ const ts_extras_1 = require("@fgv/ts-extras");
27
+ const zipArchiveFormat_1 = require("./zipArchiveFormat");
28
+ /**
29
+ * ZIP archive loader extending ts-extras foundation
30
+ * @public
31
+ */
32
+ class ZipArchiveLoader {
33
+ /**
34
+ * Load ZIP archive from File object (Browser)
35
+ * @param file - File object from file input
36
+ * @param options - Loading options
37
+ * @param onProgress - Optional progress callback
38
+ * @returns Result containing loaded archive data
39
+ */
40
+ async loadFromFile(file, options = {}, onProgress) {
41
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('reading-file', 0, `Reading file: ${file.name}`);
42
+ if (!(0, zipArchiveFormat_1.isZipFile)(file.name)) {
43
+ return (0, ts_utils_1.fail)(`File ${file.name} is not a ZIP file`);
44
+ }
45
+ try {
46
+ const buffer = await file.arrayBuffer();
47
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('reading-file', 100, 'File read complete');
48
+ return await this.loadFromBuffer(buffer, options, onProgress);
49
+ /* c8 ignore next 3 - defense in depth against internal error */
50
+ }
51
+ catch (error) {
52
+ return (0, ts_utils_1.fail)(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`);
53
+ }
54
+ }
55
+ /**
56
+ * Load ZIP archive from ArrayBuffer (Universal)
57
+ * @param buffer - ZIP data buffer
58
+ * @param options - Loading options
59
+ * @param onProgress - Optional progress callback
60
+ * @returns Result containing loaded archive data
61
+ */
62
+ async loadFromBuffer(buffer, options = {}, onProgress) {
63
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('parsing-zip', 0, 'Parsing ZIP archive');
64
+ const zipAccessorsResult = ts_extras_1.ZipFileTree.ZipFileTreeAccessors.fromBuffer(buffer);
65
+ if (zipAccessorsResult.isFailure()) {
66
+ return (0, ts_utils_1.fail)(`Failed to parse ZIP: ${zipAccessorsResult.message}`);
67
+ }
68
+ const zipAccessors = zipAccessorsResult.value;
69
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('parsing-zip', 100, 'ZIP archive parsed');
70
+ // Load manifest
71
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('loading-manifest', 0, 'Loading manifest');
72
+ const manifestResult = this._loadManifestFromAccessors(zipAccessors);
73
+ if (manifestResult.isFailure()) {
74
+ return (0, ts_utils_1.fail)(manifestResult.message);
75
+ }
76
+ const manifest = manifestResult.value;
77
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('loading-manifest', 100, 'Manifest loaded');
78
+ // Load configuration
79
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('loading-config', 0, 'Loading configuration');
80
+ const configResult = this._loadConfigurationFromAccessors(zipAccessors, manifest, options);
81
+ if (configResult.isFailure()) {
82
+ return (0, ts_utils_1.fail)(configResult.message);
83
+ }
84
+ const config = configResult.value;
85
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('loading-config', 100, 'Configuration loaded');
86
+ // Extract files and directory structure
87
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('extracting-files', 0, 'Extracting files');
88
+ const filesResult = await this._extractFilesFromAccessors(zipAccessors, onProgress);
89
+ /* c8 ignore next 3 - defense in depth against internal error */
90
+ if (filesResult.isFailure()) {
91
+ return (0, ts_utils_1.fail)(filesResult.message);
92
+ }
93
+ const { files, directory } = filesResult.value;
94
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('extracting-files', 100, `Extracted ${files.length} files`);
95
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('extracting-files', 100, 'ZIP loading complete');
96
+ return (0, ts_utils_1.succeed)({
97
+ manifest,
98
+ config,
99
+ files,
100
+ directory
101
+ });
102
+ }
103
+ /**
104
+ * Load manifest from ZIP using ZipFileTreeAccessors
105
+ * @param zipAccessors - ZIP file tree accessors
106
+ * @returns Result containing parsed manifest
107
+ */
108
+ _loadManifestFromAccessors(zipAccessors) {
109
+ const manifestResult = zipAccessors.getFileContents('manifest.json');
110
+ if (manifestResult.isFailure()) {
111
+ // Manifest is optional - return success with undefined
112
+ return (0, ts_utils_1.succeed)(undefined);
113
+ }
114
+ const parseResult = (0, zipArchiveFormat_1.parseZipArchiveManifest)(manifestResult.value);
115
+ if (parseResult.isFailure()) {
116
+ return (0, ts_utils_1.fail)(`Failed to parse manifest.json: ${parseResult.message}`);
117
+ }
118
+ return (0, ts_utils_1.succeed)(parseResult.value);
119
+ }
120
+ /**
121
+ * Load configuration from ZIP using ZipFileTreeAccessors
122
+ * @param zipAccessors - ZIP file tree accessors
123
+ * @param manifest - Parsed manifest (may be undefined)
124
+ * @param options - Loading options
125
+ * @returns Result containing parsed configuration
126
+ */
127
+ _loadConfigurationFromAccessors(zipAccessors, manifest, options) {
128
+ // Check if manifest specifies a config file
129
+ const manifestSpecifiesConfig = (manifest === null || manifest === void 0 ? void 0 : manifest.config) !== undefined;
130
+ if (!manifestSpecifiesConfig) {
131
+ // If no config specified in manifest, config is optional
132
+ return (0, ts_utils_1.succeed)(undefined);
133
+ }
134
+ // Get the config path from the manifest
135
+ const configPath = manifest.config.archivePath;
136
+ return zipAccessors
137
+ .getFileContents(configPath)
138
+ .withErrorFormat(() => `Manifest specifies config file at '${configPath}' but it was not found in archive`)
139
+ .onSuccess((configContent) => (0, zipArchiveFormat_1.parseZipArchiveConfiguration)(configContent))
140
+ .withErrorFormat((e) => `Failed to parse config file '${configPath}': ${e}`);
141
+ }
142
+ /**
143
+ * Extract files and directory structure from ZIP
144
+ * @param zipAccessors - ZIP file tree accessors
145
+ * @param onProgress - Optional progress callback
146
+ * @returns Result containing files and directory structure
147
+ */
148
+ async _extractFilesFromAccessors(zipAccessors, onProgress) {
149
+ try {
150
+ const files = [];
151
+ const directories = new Map();
152
+ // Get all children from root
153
+ const rootChildrenResult = zipAccessors.getChildren('/');
154
+ /* c8 ignore next 3 - defense in depth against internal error */
155
+ if (rootChildrenResult.isFailure()) {
156
+ return (0, ts_utils_1.fail)(`Failed to read ZIP contents: ${rootChildrenResult.message}`);
157
+ }
158
+ // Process all items recursively
159
+ await this._processFileTreeItems(zipAccessors, '/', rootChildrenResult.value, files, directories, onProgress);
160
+ // Build directory structure
161
+ const directory = this._buildDirectoryStructure(files, directories);
162
+ return (0, ts_utils_1.succeed)({ files, directory });
163
+ /* c8 ignore next 3 - defense in depth against internal error */
164
+ }
165
+ catch (error) {
166
+ return (0, ts_utils_1.fail)(`Failed to extract files: ${error instanceof Error ? error.message : String(error)}`);
167
+ }
168
+ }
169
+ /**
170
+ * Recursively process file tree items
171
+ * @param zipAccessors - ZIP file tree accessors
172
+ * @param currentPath - Current directory path
173
+ * @param items - Items to process
174
+ * @param files - Files collection
175
+ * @param directories - Directories collection
176
+ * @param onProgress - Optional progress callback
177
+ * @param processed - Processing counter
178
+ */
179
+ async _processFileTreeItems(zipAccessors, currentPath, items, files, directories, onProgress, processed = { count: 0 }) {
180
+ for (const item of items) {
181
+ /* c8 ignore next 1 - defense in depth */
182
+ const itemPath = item.absolutePath.startsWith('/') ? item.absolutePath.substring(1) : item.absolutePath;
183
+ if (item.type === 'directory') {
184
+ // Track directory
185
+ if (!directories.has(itemPath)) {
186
+ directories.set(itemPath, {
187
+ name: item.name,
188
+ files: [],
189
+ subdirectories: []
190
+ });
191
+ }
192
+ // Recursively process children
193
+ const childrenResult = zipAccessors.getChildren(item.absolutePath);
194
+ if (childrenResult.isSuccess()) {
195
+ await this._processFileTreeItems(zipAccessors, item.absolutePath, childrenResult.value, files, directories, onProgress, processed);
196
+ }
197
+ }
198
+ else if (item.type === 'file') {
199
+ // Get file content
200
+ const contentResult = item.getRawContents();
201
+ const content = contentResult.orDefault('');
202
+ files.push({
203
+ name: item.name,
204
+ path: itemPath,
205
+ content,
206
+ type: this._getFileType(item.name)
207
+ });
208
+ }
209
+ processed.count++;
210
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress('extracting-files', 0, `Processing ${itemPath}`);
211
+ }
212
+ }
213
+ /**
214
+ * Build directory structure from files
215
+ * @param files - Extracted files
216
+ * @param directories - Directories map
217
+ * @returns Root directory structure or null
218
+ */
219
+ _buildDirectoryStructure(files, directories) {
220
+ /* c8 ignore next 3 - should never happen with fflate */
221
+ if (files.length === 0) {
222
+ return undefined;
223
+ }
224
+ const rootDir = {
225
+ name: 'root',
226
+ files: [],
227
+ subdirectories: []
228
+ };
229
+ // Process files to build directory structure
230
+ for (const file of files) {
231
+ const pathParts = file.path.split('/').filter((part) => part.length > 0);
232
+ /* c8 ignore next 1 - defense in depth should never happen */
233
+ if (pathParts.length === 0)
234
+ continue;
235
+ // Ensure all parent directories exist
236
+ let currentDir = rootDir;
237
+ for (let i = 0; i < pathParts.length - 1; i++) {
238
+ const dirName = pathParts[i];
239
+ let foundDir = currentDir.subdirectories.find((d) => d.name === dirName);
240
+ if (!foundDir) {
241
+ foundDir = {
242
+ name: dirName,
243
+ files: [],
244
+ subdirectories: []
245
+ };
246
+ currentDir.subdirectories.push(foundDir);
247
+ }
248
+ currentDir = foundDir;
249
+ }
250
+ // Add file to its parent directory
251
+ currentDir.files.push(file);
252
+ }
253
+ return rootDir;
254
+ }
255
+ /**
256
+ * Get file MIME type based on extension
257
+ * @param filename - Filename
258
+ * @returns MIME type
259
+ */
260
+ _getFileType(filename) {
261
+ const ext = filename.toLowerCase().split('.').pop();
262
+ /* c8 ignore next 20 - no need to test every one */
263
+ switch (ext) {
264
+ case 'json':
265
+ return 'application/json';
266
+ case 'yaml':
267
+ case 'yml':
268
+ return 'application/yaml';
269
+ case 'xml':
270
+ return 'application/xml';
271
+ case 'txt':
272
+ return 'text/plain';
273
+ case 'md':
274
+ return 'text/markdown';
275
+ case 'js':
276
+ return 'application/javascript';
277
+ case 'ts':
278
+ return 'application/typescript';
279
+ default:
280
+ return 'application/octet-stream';
281
+ }
282
+ }
283
+ }
284
+ exports.ZipArchiveLoader = ZipArchiveLoader;
285
+ //# sourceMappingURL=zipArchiveLoader.js.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=convert.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=json.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=zipArchiveCreator.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=zipArchiveFormat.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=zipArchiveIdempotency.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ import '@fgv/ts-utils-jest';
2
+ //# sourceMappingURL=zipArchiveLoader.test.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-res",
3
- "version": "5.0.0-16",
3
+ "version": "5.0.0-18",
4
4
  "description": "Multi-dimensional Resource Runtime",
5
5
  "main": "lib/index.js",
6
6
  "types": "dist/ts-res.d.ts",
@@ -44,15 +44,16 @@
44
44
  "@rushstack/heft-node-rig": "~2.9.3",
45
45
  "@types/heft-jest": "1.0.6",
46
46
  "eslint-plugin-tsdoc": "~0.4.0",
47
- "@fgv/ts-utils-jest": "5.0.0-16",
48
- "@fgv/ts-extras": "5.0.0-16"
47
+ "@fgv/ts-utils-jest": "5.0.0-18",
48
+ "@fgv/ts-extras": "5.0.0-18"
49
49
  },
50
50
  "dependencies": {
51
51
  "luxon": "^3.7.1",
52
- "@fgv/ts-utils": "5.0.0-16",
53
- "@fgv/ts-json-base": "5.0.0-16",
54
- "@fgv/ts-bcp47": "5.0.0-16",
55
- "@fgv/ts-json": "5.0.0-16"
52
+ "fflate": "~0.8.2",
53
+ "@fgv/ts-utils": "5.0.0-18",
54
+ "@fgv/ts-json-base": "5.0.0-18",
55
+ "@fgv/ts-json": "5.0.0-18",
56
+ "@fgv/ts-bcp47": "5.0.0-18"
56
57
  },
57
58
  "scripts": {
58
59
  "build": "heft build --clean",
package/src/index.ts CHANGED
@@ -32,6 +32,7 @@ import * as Resources from './packlets/resources';
32
32
  import * as ResourceTypes from './packlets/resource-types';
33
33
  import * as Import from './packlets/import';
34
34
  import * as Runtime from './packlets/runtime';
35
+ import * as ZipArchive from './packlets/zip-archive';
35
36
 
36
37
  export * from './packlets/common';
37
38
 
@@ -69,5 +70,6 @@ export {
69
70
  ResourceType,
70
71
  ResourceTypes,
71
72
  Resources,
72
- Runtime
73
+ Runtime,
74
+ ZipArchive
73
75
  };
@@ -0,0 +1,121 @@
1
+ /*
2
+ * Copyright (c) 2025 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+ /* eslint-disable @rushstack/typedef-var */
24
+
25
+ import { Validator, Validators, Converter, Converters } from '@fgv/ts-utils';
26
+ import * as Json from './json';
27
+ import { Model as ConfigModel, Convert as ConfigConvert } from '../config';
28
+
29
+ /**
30
+ * Validator for ZIP archive input type
31
+ * @public
32
+ */
33
+ export const zipArchiveInputType: Validator<'file' | 'directory'> = Validators.enumeratedValue([
34
+ 'file',
35
+ 'directory'
36
+ ] as const);
37
+
38
+ /**
39
+ * Validator for ZIP archive config type
40
+ * @public
41
+ */
42
+ export const zipArchiveConfigType: Validator<'file'> = Validators.enumeratedValue(['file'] as const);
43
+
44
+ /**
45
+ * Validator for ZIP archive input information
46
+ * @public
47
+ */
48
+ export const zipArchiveInputInfo: Validator<Json.IZipArchiveInputInfo> =
49
+ Validators.object<Json.IZipArchiveInputInfo>({
50
+ type: zipArchiveInputType,
51
+ originalPath: Validators.string,
52
+ archivePath: Validators.string
53
+ });
54
+
55
+ /**
56
+ * Validator for ZIP archive config information
57
+ * @public
58
+ */
59
+ export const zipArchiveConfigInfo: Validator<Json.IZipArchiveConfigInfo> =
60
+ Validators.object<Json.IZipArchiveConfigInfo>({
61
+ type: zipArchiveConfigType,
62
+ originalPath: Validators.string,
63
+ archivePath: Validators.string
64
+ });
65
+
66
+ /**
67
+ * Validator for ZIP archive manifest
68
+ * Compatible with existing tools from ts-res-browser-cli
69
+ * @public
70
+ */
71
+ export const zipArchiveManifest: Validator<Json.IZipArchiveManifest> =
72
+ Validators.object<Json.IZipArchiveManifest>({
73
+ timestamp: Validators.string,
74
+ input: zipArchiveInputInfo.optional(),
75
+ config: zipArchiveConfigInfo.optional()
76
+ });
77
+
78
+ /**
79
+ * Validator for MIME type strings
80
+ * @public
81
+ */
82
+ export const mimeType: Validator<string> = Validators.string;
83
+
84
+ /**
85
+ * Converter for imported file
86
+ * @public
87
+ */
88
+ export const importedFile: Converter<Json.IImportedFile> = Converters.strictObject<Json.IImportedFile>({
89
+ name: Converters.string,
90
+ path: Converters.string,
91
+ content: Converters.string,
92
+ type: Converters.string // MIME type as string
93
+ });
94
+
95
+ /**
96
+ * Converter for imported directory structure (recursive)
97
+ * Note: Uses Converter pattern because Validators don't support recursion with self parameter
98
+ * @public
99
+ */
100
+ export const importedDirectory: Converter<Json.IImportedDirectory> = Converters.generic<
101
+ Json.IImportedDirectory,
102
+ unknown
103
+ >((from: unknown, self: Converter<Json.IImportedDirectory, unknown>, context?: unknown) => {
104
+ return Converters.strictObject<Json.IImportedDirectory>({
105
+ name: Converters.string,
106
+ files: Converters.arrayOf(importedFile),
107
+ subdirectories: Converters.arrayOf(self)
108
+ }).convert(from, context);
109
+ });
110
+
111
+ /**
112
+ * Validator for system configuration (delegates to config packlet)
113
+ * This validates that the parsed JSON conforms to the system configuration schema
114
+ * @public
115
+ */
116
+ export const systemConfiguration: Validator<ConfigModel.ISystemConfiguration> = Validators.isA(
117
+ 'system configuration',
118
+ (value: unknown): value is ConfigModel.ISystemConfiguration => {
119
+ return ConfigConvert.systemConfiguration.convert(value).isSuccess();
120
+ }
121
+ );
@@ -0,0 +1,76 @@
1
+ /*
2
+ * Copyright (c) 2025 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+ /**
24
+ * ZIP archive functionality for ts-res source file archives
25
+ *
26
+ * This packlet provides consolidated ZIP archive creation and loading functionality
27
+ * for source files, compatible with existing tools while using fflate for
28
+ * universal browser compatibility.
29
+ *
30
+ * @remarks
31
+ * ZIP archives contain source files for resource ingestion with directory
32
+ * structure preserved and optional validation but no processing or transformation.
33
+ * This is distinct from ZIP bundles which contain processed resource output.
34
+ *
35
+ * @packageDocumentation
36
+ */
37
+
38
+ import * as Json from './json';
39
+ import * as Convert from './convert';
40
+
41
+ // Namespaces
42
+ export { Json, Convert };
43
+
44
+ // Core classes
45
+ export { ZipArchiveCreator } from './zipArchiveCreator';
46
+ export { ZipArchiveLoader } from './zipArchiveLoader';
47
+
48
+ // Types and interfaces
49
+ export type {
50
+ IZipArchivePathOptions,
51
+ IZipArchiveFileTreeOptions,
52
+ ZipArchiveOptions,
53
+ IZipArchiveResult,
54
+ IZipArchiveManifest,
55
+ IZipArchiveLoadOptions,
56
+ IZipArchiveLoadResult,
57
+ IImportedFile,
58
+ IImportedDirectory,
59
+ ZipArchiveProgressCallback
60
+ } from './types';
61
+
62
+ // Format utilities
63
+ export {
64
+ createZipArchiveManifest,
65
+ parseZipArchiveManifest,
66
+ validateZipArchiveManifest,
67
+ parseZipArchiveConfiguration,
68
+ generateZipArchiveFilename,
69
+ normalizePath,
70
+ getDirectoryName,
71
+ sanitizeFilename,
72
+ isZipFile
73
+ } from './zipArchiveFormat';
74
+
75
+ // Constants
76
+ export { ZipArchiveConstants } from './types';
@@ -0,0 +1,91 @@
1
+ /*
2
+ * Copyright (c) 2025 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+ /**
24
+ * JSON representation of ZIP archive input information
25
+ * @public
26
+ */
27
+ export interface IZipArchiveInputInfo {
28
+ /** Type of input (file or directory) */
29
+ type: 'file' | 'directory';
30
+ /** Original file/directory path */
31
+ originalPath: string;
32
+ /** Path within the archive (e.g., "input/mydir") */
33
+ archivePath: string;
34
+ }
35
+
36
+ /**
37
+ * JSON representation of ZIP archive config information
38
+ * @public
39
+ */
40
+ export interface IZipArchiveConfigInfo {
41
+ /** Type of config (always 'file') */
42
+ type: 'file';
43
+ /** Original config file path */
44
+ originalPath: string;
45
+ /** Path within the archive (e.g., "config.json") */
46
+ archivePath: string;
47
+ }
48
+
49
+ /**
50
+ * JSON representation of a ZIP archive manifest
51
+ * Compatible with existing tools from ts-res-browser-cli
52
+ * @public
53
+ */
54
+ export interface IZipArchiveManifest {
55
+ /** Archive creation timestamp */
56
+ timestamp: string;
57
+
58
+ /** Optional input source information */
59
+ input?: IZipArchiveInputInfo;
60
+
61
+ /** Optional configuration file information */
62
+ config?: IZipArchiveConfigInfo;
63
+ }
64
+
65
+ /**
66
+ * JSON representation of an imported file
67
+ * @public
68
+ */
69
+ export interface IImportedFile {
70
+ /** File name */
71
+ name: string;
72
+ /** Full path within archive */
73
+ path: string;
74
+ /** File content as string */
75
+ content: string;
76
+ /** MIME type */
77
+ type: string;
78
+ }
79
+
80
+ /**
81
+ * JSON representation of an imported directory structure
82
+ * @public
83
+ */
84
+ export interface IImportedDirectory {
85
+ /** Directory name */
86
+ name: string;
87
+ /** Files in this directory */
88
+ files: IImportedFile[];
89
+ /** Subdirectories */
90
+ subdirectories: IImportedDirectory[];
91
+ }