@fulmenhq/tsfulmen 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CHANGELOG.md +76 -1
  2. package/README.md +30 -1
  3. package/config/crucible-ts/taxonomy/metrics.yaml +1 -1
  4. package/dist/appidentity/index.js +9 -2209
  5. package/dist/appidentity/index.js.map +1 -1
  6. package/dist/assets/index.d.ts +184 -0
  7. package/dist/assets/index.js +4 -0
  8. package/dist/assets/index.js.map +1 -0
  9. package/dist/bin/prometheus-cli.js +15 -9139
  10. package/dist/bin/prometheus-cli.js.map +1 -1
  11. package/dist/bin/schema-cli.js +4 -6098
  12. package/dist/bin/schema-cli.js.map +1 -1
  13. package/dist/bin/signals-cli.js +15 -1910
  14. package/dist/bin/signals-cli.js.map +1 -1
  15. package/dist/chunk-24BAF63H.js +194 -0
  16. package/dist/chunk-24BAF63H.js.map +1 -0
  17. package/dist/chunk-3HISR2XI.js +36 -0
  18. package/dist/chunk-3HISR2XI.js.map +1 -0
  19. package/dist/chunk-56F2EEAG.js +328 -0
  20. package/dist/chunk-56F2EEAG.js.map +1 -0
  21. package/dist/chunk-5HZEJGZ5.js +725 -0
  22. package/dist/chunk-5HZEJGZ5.js.map +1 -0
  23. package/dist/chunk-7YWACZEJ.js +837 -0
  24. package/dist/chunk-7YWACZEJ.js.map +1 -0
  25. package/dist/chunk-BCO3FUUO.js +682 -0
  26. package/dist/chunk-BCO3FUUO.js.map +1 -0
  27. package/dist/chunk-GOTEQYAD.js +410 -0
  28. package/dist/chunk-GOTEQYAD.js.map +1 -0
  29. package/dist/chunk-HY6JQOFN.js +32 -0
  30. package/dist/chunk-HY6JQOFN.js.map +1 -0
  31. package/dist/chunk-LMNAUTW7.js +43 -0
  32. package/dist/chunk-LMNAUTW7.js.map +1 -0
  33. package/dist/chunk-LWAH2UG7.js +103 -0
  34. package/dist/chunk-LWAH2UG7.js.map +1 -0
  35. package/dist/chunk-OHE2AOH4.js +273 -0
  36. package/dist/chunk-OHE2AOH4.js.map +1 -0
  37. package/dist/chunk-P3DNOZOG.js +1488 -0
  38. package/dist/chunk-P3DNOZOG.js.map +1 -0
  39. package/dist/chunk-PQ6TKPUN.js +27 -0
  40. package/dist/chunk-PQ6TKPUN.js.map +1 -0
  41. package/dist/chunk-QCCN6POA.js +211 -0
  42. package/dist/chunk-QCCN6POA.js.map +1 -0
  43. package/dist/chunk-QGTNV4U2.js +985 -0
  44. package/dist/chunk-QGTNV4U2.js.map +1 -0
  45. package/dist/chunk-RSLFR4TO.js +631 -0
  46. package/dist/chunk-RSLFR4TO.js.map +1 -0
  47. package/dist/chunk-SNV5WG5V.js +9412 -0
  48. package/dist/chunk-SNV5WG5V.js.map +1 -0
  49. package/dist/chunk-UP2VWCW5.js +33 -0
  50. package/dist/chunk-UP2VWCW5.js.map +1 -0
  51. package/dist/chunk-VJIGX7MH.js +208 -0
  52. package/dist/chunk-VJIGX7MH.js.map +1 -0
  53. package/dist/chunk-WCCMRVZF.js +130 -0
  54. package/dist/chunk-WCCMRVZF.js.map +1 -0
  55. package/dist/chunk-WSMVLWLB.js +269 -0
  56. package/dist/chunk-WSMVLWLB.js.map +1 -0
  57. package/dist/config/index.js +6 -1220
  58. package/dist/config/index.js.map +1 -1
  59. package/dist/crucible/fulpack/index.js +2 -25
  60. package/dist/crucible/fulpack/index.js.map +1 -1
  61. package/dist/crucible/index.js +13 -1129
  62. package/dist/crucible/index.js.map +1 -1
  63. package/dist/docscribe/index.js +2 -680
  64. package/dist/docscribe/index.js.map +1 -1
  65. package/dist/errors/index.js +5 -1831
  66. package/dist/errors/index.js.map +1 -1
  67. package/dist/errors-D7KXV3QW.js +4 -0
  68. package/dist/errors-D7KXV3QW.js.map +1 -0
  69. package/dist/errors-R7ZODJXS.js +8 -0
  70. package/dist/errors-R7ZODJXS.js.map +1 -0
  71. package/dist/export-EA3RNQNS.js +6 -0
  72. package/dist/export-EA3RNQNS.js.map +1 -0
  73. package/dist/foundry/index.js +340 -3949
  74. package/dist/foundry/index.js.map +1 -1
  75. package/dist/foundry/similarity/index.js +3 -134
  76. package/dist/foundry/similarity/index.js.map +1 -1
  77. package/dist/fulencode/index.js +3 -319
  78. package/dist/fulencode/index.js.map +1 -1
  79. package/dist/fulhash/index.js +3 -611
  80. package/dist/fulhash/index.js.map +1 -1
  81. package/dist/fulpack/index.js +3 -1515
  82. package/dist/fulpack/index.js.map +1 -1
  83. package/dist/hash-YOJYVOWH.js +4 -0
  84. package/dist/hash-YOJYVOWH.js.map +1 -0
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.js +14 -4395
  87. package/dist/index.js.map +1 -1
  88. package/dist/loader-VP2ZIC4O.js +9 -0
  89. package/dist/loader-VP2ZIC4O.js.map +1 -0
  90. package/dist/logging/index.js +2 -723
  91. package/dist/logging/index.js.map +1 -1
  92. package/dist/pathfinder/index.js +18 -2341
  93. package/dist/pathfinder/index.js.map +1 -1
  94. package/dist/prom-client-PNEBGD3G.js +4801 -0
  95. package/dist/prom-client-PNEBGD3G.js.map +1 -0
  96. package/dist/reports/license-inventory.csv +1 -1
  97. package/dist/schema/index.d.ts +19 -6
  98. package/dist/schema/index.js +4 -6148
  99. package/dist/schema/index.js.map +1 -1
  100. package/dist/signals/index.js +8 -2767
  101. package/dist/signals/index.js.map +1 -1
  102. package/dist/similarity/index.js +3 -134
  103. package/dist/similarity/index.js.map +1 -1
  104. package/dist/telemetry/http/index.js +9 -930
  105. package/dist/telemetry/http/index.js.map +1 -1
  106. package/dist/telemetry/index.js +4 -1561
  107. package/dist/telemetry/index.js.map +1 -1
  108. package/dist/telemetry/prometheus/index.js +7 -8422
  109. package/dist/telemetry/prometheus/index.js.map +1 -1
  110. package/dist/validator-I3ASDVDB.js +6 -0
  111. package/dist/validator-I3ASDVDB.js.map +1 -0
  112. package/package.json +5 -1
  113. package/schemas/crucible-ts/library/module-manifest/v1.0.0/module-manifest.schema.json +1 -1
  114. package/schemas/crucible-ts/observability/logging/v1.0.0/definitions.schema.json +1 -1
  115. package/schemas/crucible-ts/observability/logging/v1.0.0/log-event.schema.json +13 -13
  116. package/schemas/crucible-ts/observability/logging/v1.0.0/logger-config.schema.json +2 -2
  117. package/schemas/crucible-ts/observability/logging/v1.0.0/logging-policy.schema.json +1 -1
  118. package/schemas/crucible-ts/observability/logging/v1.0.0/middleware-config.schema.json +2 -2
  119. package/schemas/crucible-ts/observability/logging/v1.0.0/severity-filter.schema.json +3 -3
  120. package/schemas/crucible-ts/upstream/3leaps/crucible/PROVENANCE.md +3 -3
  121. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/catalog/classifiers/README.md +1 -1
  122. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/classifiers-framework.md +1 -1
  123. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/data-sensitivity-classification.md +1 -1
package/dist/index.js CHANGED
@@ -1,4400 +1,19 @@
1
- import addFormats from 'ajv-formats';
2
- import 'child_process';
3
- import { readFile, access } from 'fs/promises';
4
- import { parse, parseDocument } from 'yaml';
5
- import { join, dirname, basename, relative, extname } from 'path';
6
- import { fileURLToPath } from 'url';
7
- import glob from 'fast-glob';
8
- import Ajv from 'ajv';
9
- import Ajv2019 from 'ajv/dist/2019.js';
10
- import Ajv2020 from 'ajv/dist/2020.js';
11
- import AjvDraft04 from 'ajv-draft-04';
12
- import 'commander';
13
- import 'crypto';
14
- import { existsSync, mkdirSync, statSync, createReadStream, createWriteStream, readdirSync, lstatSync } from 'fs';
15
- import { pipeline } from 'stream';
16
- import { createGunzip, createGzip } from 'zlib';
17
- import { TarArchive, ZipArchive } from 'archiver';
18
-
19
- var __getOwnPropNames = Object.getOwnPropertyNames;
20
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
21
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
22
- }) : x)(function(x) {
23
- if (typeof require !== "undefined") return require.apply(this, arguments);
24
- throw Error('Dynamic require of "' + x + '" is not supported');
25
- });
26
- var __esm = (fn, res) => function __init() {
27
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
28
- };
29
-
30
- // src/appidentity/constants.ts
31
- var APP_IDENTITY_FILENAME, APP_IDENTITY_DIR, APP_IDENTITY_ENV_VAR, APP_IDENTITY_SCHEMA_ID, MAX_ANCESTOR_SEARCH_DEPTH;
32
- var init_constants = __esm({
33
- "src/appidentity/constants.ts"() {
34
- APP_IDENTITY_FILENAME = "app.yaml";
35
- APP_IDENTITY_DIR = ".fulmen";
36
- APP_IDENTITY_ENV_VAR = "FULMEN_APP_IDENTITY_PATH";
37
- APP_IDENTITY_SCHEMA_ID = "config/repository/app-identity/v1.0.0/app-identity";
38
- MAX_ANCESTOR_SEARCH_DEPTH = 20;
39
- }
40
- });
41
- function applyFulmenAjvFormats(ajv, options = {}) {
42
- const mode = options.mode ?? "fast";
43
- const formats = options.formats ?? DEFAULT_FORMATS;
44
- addFormats(ajv, { mode, formats });
45
- return ajv;
46
- }
47
- var DEFAULT_FORMATS;
48
- var init_ajv_formats = __esm({
49
- "src/schema/ajv-formats.ts"() {
50
- DEFAULT_FORMATS = [
51
- "date-time",
52
- "email",
53
- "hostname",
54
- "ipv4",
55
- "ipv6",
56
- "uri",
57
- "uri-reference",
58
- "uuid"
59
- ];
60
- }
61
- });
62
-
63
- // src/schema/errors.ts
64
- var SchemaValidationError;
65
- var init_errors = __esm({
66
- "src/schema/errors.ts"() {
67
- SchemaValidationError = class _SchemaValidationError extends Error {
68
- constructor(message, schemaId, diagnostics = [], source, cause) {
69
- super(message);
70
- this.schemaId = schemaId;
71
- this.diagnostics = diagnostics;
72
- this.source = source;
73
- this.cause = cause;
74
- this.name = "SchemaValidationError";
75
- if (Error.captureStackTrace) {
76
- Error.captureStackTrace(this, _SchemaValidationError);
77
- }
78
- }
79
- /**
80
- * Create error for schema not found
81
- */
82
- static schemaNotFound(schemaId) {
83
- return new _SchemaValidationError(`Schema not found: ${schemaId}`, schemaId);
84
- }
85
- /**
86
- * Create error for invalid schema input
87
- */
88
- static invalidSchemaInput(source, details) {
89
- return new _SchemaValidationError(`Invalid schema input: ${details}`, void 0, [], source);
90
- }
91
- /**
92
- * Create error for validation failure
93
- */
94
- static validationFailed(schemaId, diagnostics, source) {
95
- const errorCount = diagnostics.filter((d) => d.severity === "ERROR").length;
96
- const warningCount = diagnostics.filter((d) => d.severity === "WARN").length;
97
- const message = `Schema validation failed: ${errorCount} error(s), ${warningCount} warning(s)`;
98
- return new _SchemaValidationError(message, schemaId, diagnostics, source);
99
- }
100
- /**
101
- * Create error for goneat binary not found
102
- */
103
- static goneatNotFound(goneatPath) {
104
- const pathInfo = goneatPath ? ` at ${goneatPath}` : "";
105
- return new _SchemaValidationError(
106
- `Goneat binary not found${pathInfo}. Falling back to AJV validation.`
107
- );
108
- }
109
- /**
110
- * Create error for goneat execution failure
111
- */
112
- static goneatExecutionFailed(error) {
113
- return new _SchemaValidationError(
114
- "Goneat execution failed. Falling back to AJV validation.",
115
- void 0,
116
- [],
117
- void 0,
118
- error
119
- );
120
- }
121
- /**
122
- * Create error for empty schema input
123
- */
124
- static emptySchemaInput(source) {
125
- return new _SchemaValidationError("Schema content is empty", void 0, [], source);
126
- }
127
- /**
128
- * Create error for parse failure
129
- */
130
- static parseFailed(source, error) {
131
- return new _SchemaValidationError(
132
- `Failed to parse schema: ${error.message}`,
133
- void 0,
134
- [],
135
- source,
136
- error
137
- );
138
- }
139
- /**
140
- * Create error for encoding failure
141
- */
142
- static encodingFailed(source, error) {
143
- return new _SchemaValidationError(
144
- `Failed to encode schema: ${error.message}`,
145
- void 0,
146
- [],
147
- source,
148
- error
149
- );
150
- }
151
- /**
152
- * Create error for registry operation failure
153
- */
154
- static registryError(operation, details) {
155
- return new _SchemaValidationError(`Schema registry ${operation} failed: ${details}`);
156
- }
157
- /**
158
- * Format error for display
159
- */
160
- format() {
161
- let output = this.message;
162
- if (this.schemaId) {
163
- output += `
164
- Schema ID: ${this.schemaId}`;
165
- }
166
- if (this.diagnostics.length > 0) {
167
- output += "\n\nValidation Issues:";
168
- this.diagnostics.forEach((diag, index) => {
169
- output += `
170
- ${index + 1}. [${diag.severity}] ${diag.message}`;
171
- if (diag.pointer) {
172
- output += ` at ${diag.pointer}`;
173
- }
174
- if (diag.keyword) {
175
- output += ` (keyword: ${diag.keyword})`;
176
- }
177
- if (diag.source) {
178
- output += ` [${diag.source}]`;
179
- }
180
- });
181
- }
182
- if (this.source) {
183
- output += `
184
-
185
- Source: ${this.source.type}`;
186
- if (this.source.id) {
187
- output += ` (${this.source.id})`;
188
- }
189
- }
190
- return output;
191
- }
192
- /**
193
- * Convert to JSON representation
194
- */
195
- toJSON() {
196
- return {
197
- name: this.name,
198
- message: this.message,
199
- schemaId: this.schemaId,
200
- diagnostics: this.diagnostics,
201
- source: this.source,
202
- cause: this.cause?.message
203
- };
204
- }
205
- };
206
- }
207
- });
208
-
209
- // src/schema/utils.ts
210
- function createDiagnostic(pointer, message, keyword, severity = "ERROR", source = "ajv", data) {
211
- return {
212
- pointer,
213
- message,
214
- keyword,
215
- severity,
216
- source,
217
- data
218
- };
219
- }
220
- var init_utils = __esm({
221
- "src/schema/utils.ts"() {
222
- init_errors();
223
- }
224
- });
225
- var init_goneat_bridge = __esm({
226
- "src/schema/goneat-bridge.ts"() {
227
- init_utils();
228
- }
229
- });
230
- var init_normalizer = __esm({
231
- "src/schema/normalizer.ts"() {
232
- init_errors();
233
- }
234
- });
235
- function optionsChanged(newOptions) {
236
- if (!globalRegistryOptions) return false;
237
- return true;
238
- }
239
- function getSchemaRegistry(options) {
240
- if (!globalRegistry || optionsChanged()) {
241
- globalRegistry = new SchemaRegistry(options);
242
- globalRegistryOptions = options;
243
- }
244
- return globalRegistry;
245
- }
246
- var DEFAULT_PATTERNS, SchemaRegistry, globalRegistry, globalRegistryOptions;
247
- var init_registry = __esm({
248
- "src/schema/registry.ts"() {
249
- init_errors();
250
- DEFAULT_PATTERNS = ["**/*.schema.json", "**/*.schema.yaml", "**/*.schema.yml"];
251
- SchemaRegistry = class {
252
- schemas = /* @__PURE__ */ new Map();
253
- options;
254
- constructor(options = {}) {
255
- this.options = {
256
- baseDir: options.baseDir || this.getDefaultSchemaDir(),
257
- patterns: options.patterns || DEFAULT_PATTERNS,
258
- followSymlinks: options.followSymlinks ?? false,
259
- maxDepth: options.maxDepth ?? 10
260
- };
261
- }
262
- /**
263
- * Get default schema directory using import.meta.url
264
- */
265
- getDefaultSchemaDir() {
266
- const __filename = fileURLToPath(import.meta.url);
267
- const __dirname2 = dirname(__filename);
268
- return join(__dirname2, "..", "..", "schemas", "crucible-ts");
269
- }
270
- /**
271
- * Build logical schema ID from file path
272
- */
273
- buildSchemaId(filePath, baseDir) {
274
- const relativePath = relative(baseDir, filePath);
275
- const withoutExt = relativePath.replace(/\.(schema\.(json|yaml|yml))$/, "");
276
- return withoutExt.replace(/\\/g, "/");
277
- }
278
- /**
279
- * Extract schema format from file extension
280
- */
281
- getSchemaFormat(filePath) {
282
- const ext = extname(filePath).toLowerCase();
283
- switch (ext) {
284
- case ".json":
285
- return "json";
286
- case ".yaml":
287
- case ".yml":
288
- return "yaml";
289
- default:
290
- return "json";
291
- }
292
- }
293
- /**
294
- * Extract metadata from schema file
295
- */
296
- async extractMetadata(filePath) {
297
- try {
298
- const content = await readFile(filePath, "utf-8");
299
- const format = this.getSchemaFormat(filePath);
300
- let parsed;
301
- if (format === "yaml") {
302
- parsed = parse(content);
303
- } else {
304
- parsed = JSON.parse(content);
305
- }
306
- const baseDir = this.options.baseDir ?? "";
307
- const relativePath = relative(baseDir, filePath);
308
- return {
309
- id: this.buildSchemaId(filePath, baseDir),
310
- path: filePath,
311
- relativePath,
312
- format,
313
- version: parsed.$schema || parsed.version,
314
- description: parsed.title || parsed.description,
315
- schemaDraft: parsed.$schema
316
- };
317
- } catch (error) {
318
- throw SchemaValidationError.registryError(
319
- "metadata extraction",
320
- `Failed to process ${filePath}: ${error.message}`
321
- );
322
- }
323
- }
324
- /**
325
- * Discover and index all available schemas
326
- */
327
- async discoverSchemas() {
328
- try {
329
- const baseDir = this.options.baseDir ?? "";
330
- const patterns = this.options.patterns ?? [];
331
- if (patterns.length === 0) {
332
- this.schemas.clear();
333
- return;
334
- }
335
- const pattern = patterns.map((p) => join(baseDir, p));
336
- try {
337
- await access(baseDir);
338
- } catch {
339
- this.schemas.clear();
340
- return;
341
- }
342
- const files = await glob(pattern, {
343
- absolute: true,
344
- followSymbolicLinks: this.options.followSymlinks,
345
- deep: this.options.maxDepth,
346
- onlyFiles: true,
347
- suppressErrors: true
348
- // Don't throw on permission errors
349
- });
350
- this.schemas.clear();
351
- for (const filePath of files) {
352
- try {
353
- const metadata = await this.extractMetadata(filePath);
354
- this.schemas.set(metadata.id, metadata);
355
- } catch (error) {
356
- console.warn(`Warning: Failed to process schema ${filePath}:`, error);
357
- }
358
- }
359
- } catch (error) {
360
- throw SchemaValidationError.registryError("discovery", error.message);
361
- }
362
- }
363
- /**
364
- * List available schemas with optional prefix filtering
365
- */
366
- async listSchemas(prefix) {
367
- if (this.schemas.size === 0) {
368
- await this.discoverSchemas();
369
- }
370
- const schemas = Array.from(this.schemas.values());
371
- if (prefix) {
372
- return schemas.filter((schema) => schema.id.startsWith(prefix));
373
- }
374
- return schemas;
375
- }
376
- /**
377
- * Get schema by logical ID
378
- */
379
- async getSchema(id) {
380
- if (this.schemas.size === 0) {
381
- await this.discoverSchemas();
382
- }
383
- const schema = this.schemas.get(id);
384
- if (!schema) {
385
- throw SchemaValidationError.schemaNotFound(id);
386
- }
387
- return schema;
388
- }
389
- /**
390
- * Get schema by file path
391
- */
392
- async getSchemaByPath(filePath) {
393
- if (this.schemas.size === 0) {
394
- await this.discoverSchemas();
395
- }
396
- const absolutePath = filePath.startsWith("/") ? filePath : join(process.cwd(), filePath);
397
- for (const schema of this.schemas.values()) {
398
- if (schema.path === absolutePath) {
399
- return schema;
400
- }
401
- }
402
- throw SchemaValidationError.schemaNotFound(filePath);
403
- }
404
- /**
405
- * Check if schema exists
406
- */
407
- async hasSchema(id) {
408
- if (this.schemas.size === 0) {
409
- await this.discoverSchemas();
410
- }
411
- return this.schemas.has(id);
412
- }
413
- /**
414
- * Get registry size
415
- */
416
- get size() {
417
- return this.schemas.size;
418
- }
419
- /**
420
- * Clear registry cache
421
- */
422
- clear() {
423
- this.schemas.clear();
424
- }
425
- };
426
- }
427
- });
428
-
429
- // src/telemetry/counter.ts
430
- var Counter;
431
- var init_counter = __esm({
432
- "src/telemetry/counter.ts"() {
433
- Counter = class {
434
- constructor(name) {
435
- this.name = name;
436
- }
437
- value = 0;
438
- labeledValues = /* @__PURE__ */ new Map();
439
- /**
440
- * Increment counter by delta (default: 1)
441
- *
442
- * @param delta - Amount to increment (must be non-negative)
443
- * @param labels - Optional label dimensions for this observation
444
- * @throws {Error} If delta is negative
445
- *
446
- * @example
447
- * ```typescript
448
- * counter.inc(); // Increment unlabeled by 1
449
- * counter.inc(5); // Increment unlabeled by 5
450
- * counter.inc(1, { status: '200' }); // Increment labeled instance
451
- * counter.inc(1, { result: 'success' }); // Different label set
452
- * ```
453
- */
454
- inc(delta = 1, labels) {
455
- if (delta < 0) {
456
- throw new Error(`Counter delta must be non-negative, got: ${delta}`);
457
- }
458
- if (labels && Object.keys(labels).length > 0) {
459
- const labelKey = this.serializeLabels(labels);
460
- const current = this.labeledValues.get(labelKey) || 0;
461
- this.labeledValues.set(labelKey, current + delta);
462
- } else {
463
- this.value += delta;
464
- }
465
- }
466
- /**
467
- * Get current counter value (unlabeled)
468
- */
469
- getValue() {
470
- return this.value;
471
- }
472
- /**
473
- * Get all labeled values
474
- * @returns Map of serialized label keys to values
475
- */
476
- getLabeledValues() {
477
- return new Map(this.labeledValues);
478
- }
479
- /**
480
- * Get value for specific label combination
481
- */
482
- getValueForLabels(labels) {
483
- const labelKey = this.serializeLabels(labels);
484
- return this.labeledValues.get(labelKey) || 0;
485
- }
486
- /**
487
- * Reset counter to zero (all label combinations)
488
- */
489
- reset() {
490
- this.value = 0;
491
- this.labeledValues.clear();
492
- }
493
- /**
494
- * Serialize labels to deterministic string key
495
- * Format: key1=value1,key2=value2 (sorted by key)
496
- */
497
- serializeLabels(labels) {
498
- return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
499
- }
500
- };
501
- }
502
- });
503
-
504
- // src/telemetry/gauge.ts
505
- var Gauge;
506
- var init_gauge = __esm({
507
- "src/telemetry/gauge.ts"() {
508
- Gauge = class {
509
- constructor(name) {
510
- this.name = name;
511
- }
512
- value = 0;
513
- labeledValues = /* @__PURE__ */ new Map();
514
- /**
515
- * Set gauge to specific value
516
- *
517
- * @param value - New gauge value (can be any number, including negative)
518
- * @param labels - Optional label dimensions for this observation
519
- *
520
- * @example
521
- * ```typescript
522
- * gauge.set(42); // Set unlabeled to 42
523
- * gauge.set(-10); // Negative values allowed
524
- * gauge.set(1, { phase: 'collect' }); // Set labeled instance
525
- * ```
526
- */
527
- set(value, labels) {
528
- if (labels && Object.keys(labels).length > 0) {
529
- const labelKey = this.serializeLabels(labels);
530
- this.labeledValues.set(labelKey, value);
531
- } else {
532
- this.value = value;
533
- }
534
- }
535
- /**
536
- * Increment gauge by delta (default: 1)
537
- *
538
- * @param delta - Amount to increment (can be negative)
539
- * @param labels - Optional label dimensions for this observation
540
- */
541
- inc(delta = 1, labels) {
542
- if (labels && Object.keys(labels).length > 0) {
543
- const labelKey = this.serializeLabels(labels);
544
- const current = this.labeledValues.get(labelKey) || 0;
545
- this.labeledValues.set(labelKey, current + delta);
546
- } else {
547
- this.value += delta;
548
- }
549
- }
550
- /**
551
- * Decrement gauge by delta (default: 1)
552
- *
553
- * @param delta - Amount to decrement (can be negative)
554
- * @param labels - Optional label dimensions for this observation
555
- */
556
- dec(delta = 1, labels) {
557
- if (labels && Object.keys(labels).length > 0) {
558
- const labelKey = this.serializeLabels(labels);
559
- const current = this.labeledValues.get(labelKey) || 0;
560
- this.labeledValues.set(labelKey, current - delta);
561
- } else {
562
- this.value -= delta;
563
- }
564
- }
565
- /**
566
- * Get current gauge value (unlabeled)
567
- */
568
- getValue() {
569
- return this.value;
570
- }
571
- /**
572
- * Get all labeled values
573
- * @returns Map of serialized label keys to values
574
- */
575
- getLabeledValues() {
576
- return new Map(this.labeledValues);
577
- }
578
- /**
579
- * Get value for specific label combination
580
- */
581
- getValueForLabels(labels) {
582
- const labelKey = this.serializeLabels(labels);
583
- return this.labeledValues.get(labelKey) || 0;
584
- }
585
- /**
586
- * Reset gauge to zero (all label combinations)
587
- */
588
- reset() {
589
- this.value = 0;
590
- this.labeledValues.clear();
591
- }
592
- /**
593
- * Serialize labels to deterministic string key
594
- * Format: key1=value1,key2=value2 (sorted by key)
595
- */
596
- serializeLabels(labels) {
597
- return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
598
- }
599
- };
600
- }
601
- });
602
- async function getDefaultUnit(name) {
603
- return TaxonomyLoader.getInstance().getDefaultUnit(name);
604
- }
605
- var DEFAULT_MS_BUCKETS, TaxonomyLoader;
606
- var init_taxonomy = __esm({
607
- "src/telemetry/taxonomy.ts"() {
608
- DEFAULT_MS_BUCKETS = [1, 5, 10, 50, 100, 500, 1e3, 5e3, 1e4];
609
- TaxonomyLoader = class _TaxonomyLoader {
610
- static instance;
611
- taxonomy = null;
612
- loadPromise = null;
613
- loadError = null;
614
- constructor() {
615
- }
616
- /**
617
- * Get singleton instance
618
- */
619
- static getInstance() {
620
- if (!_TaxonomyLoader.instance) {
621
- _TaxonomyLoader.instance = new _TaxonomyLoader();
622
- }
623
- return _TaxonomyLoader.instance;
624
- }
625
- /**
626
- * Load taxonomy from YAML file
627
- */
628
- async load() {
629
- if (this.taxonomy !== null) {
630
- return this.taxonomy;
631
- }
632
- if (this.loadError !== null) {
633
- throw this.loadError;
634
- }
635
- if (this.loadPromise) {
636
- return this.loadPromise;
637
- }
638
- this.loadPromise = (async () => {
639
- try {
640
- const taxonomyPath = join(
641
- __dirname,
642
- "..",
643
- "..",
644
- "config",
645
- "crucible-ts",
646
- "taxonomy",
647
- "metrics.yaml"
648
- );
649
- const content = await readFile(taxonomyPath, "utf-8");
650
- this.taxonomy = parse(content);
651
- return this.taxonomy;
652
- } catch (err) {
653
- this.loadError = err instanceof Error ? err : new Error(String(err));
654
- throw new Error(`Failed to load metrics taxonomy: ${this.loadError.message}`);
655
- }
656
- })();
657
- return this.loadPromise;
658
- }
659
- /**
660
- * Get taxonomy (async)
661
- */
662
- async getTaxonomy() {
663
- return this.load();
664
- }
665
- /**
666
- * Get metric definition by name
667
- */
668
- async getMetric(name) {
669
- const taxonomy = await this.load();
670
- return taxonomy.metrics.find((m) => m.name === name);
671
- }
672
- /**
673
- * Get default unit for metric
674
- */
675
- async getDefaultUnit(name) {
676
- const metric = await this.getMetric(name);
677
- return metric?.unit;
678
- }
679
- /**
680
- * Get default histogram buckets for metric
681
- * Returns ADR-0007 buckets for _ms metrics, undefined for others
682
- */
683
- async getDefaultBuckets(name) {
684
- if (name.endsWith("_ms")) {
685
- const taxonomy = await this.load();
686
- return taxonomy.defaults.histogram_buckets.ms_metrics;
687
- }
688
- return void 0;
689
- }
690
- /**
691
- * Check if metric name is valid (exists in taxonomy)
692
- */
693
- async isValidMetricName(name) {
694
- try {
695
- const taxonomy = await this.load();
696
- return taxonomy.metrics.some((m) => m.name === name);
697
- } catch {
698
- return false;
699
- }
700
- }
701
- /**
702
- * Reset loader state (for testing)
703
- * @internal
704
- */
705
- static _reset() {
706
- _TaxonomyLoader.instance = new _TaxonomyLoader();
707
- }
708
- };
709
- }
710
- });
711
-
712
- // src/telemetry/histogram.ts
713
- var Histogram;
714
- var init_histogram = __esm({
715
- "src/telemetry/histogram.ts"() {
716
- init_taxonomy();
717
- Histogram = class {
718
- constructor(name, options) {
719
- this.name = name;
720
- if (options?.buckets) {
721
- this.buckets = [...options.buckets].sort((a, b) => a - b);
722
- } else if (name.endsWith("_ms") || name.endsWith("_seconds")) {
723
- this.buckets = [...DEFAULT_MS_BUCKETS];
724
- } else {
725
- this.buckets = [];
726
- }
727
- for (const bucket of this.buckets) {
728
- this.bucketCounts.set(bucket, 0);
729
- }
730
- }
731
- count = 0;
732
- sum = 0;
733
- bucketCounts = /* @__PURE__ */ new Map();
734
- labeledStates = /* @__PURE__ */ new Map();
735
- buckets;
736
- /**
737
- * Record an observation
738
- *
739
- * @param value - Value to observe (typically a duration in ms or seconds)
740
- * @param labels - Optional label dimensions for this observation
741
- *
742
- * @example
743
- * ```typescript
744
- * const start = performance.now();
745
- * // ... operation ...
746
- * histogram.observe(performance.now() - start);
747
- * histogram.observe(duration, { phase: 'collect', result: 'success' });
748
- * ```
749
- */
750
- observe(value, labels) {
751
- if (labels && Object.keys(labels).length > 0) {
752
- const labelKey = this.serializeLabels(labels);
753
- let state = this.labeledStates.get(labelKey);
754
- if (!state) {
755
- state = {
756
- count: 0,
757
- sum: 0,
758
- bucketCounts: /* @__PURE__ */ new Map()
759
- };
760
- for (const bucket of this.buckets) {
761
- state.bucketCounts.set(bucket, 0);
762
- }
763
- this.labeledStates.set(labelKey, state);
764
- }
765
- state.count++;
766
- state.sum += value;
767
- for (const bucket of this.buckets) {
768
- if (value <= bucket) {
769
- state.bucketCounts.set(bucket, (state.bucketCounts.get(bucket) || 0) + 1);
770
- }
771
- }
772
- } else {
773
- this.count++;
774
- this.sum += value;
775
- for (const bucket of this.buckets) {
776
- if (value <= bucket) {
777
- this.bucketCounts.set(bucket, (this.bucketCounts.get(bucket) || 0) + 1);
778
- }
779
- }
780
- }
781
- }
782
- /**
783
- * Get histogram summary
784
- *
785
- * Returns OTLP-compatible histogram summary with cumulative bucket counts.
786
- */
787
- getSummary() {
788
- const buckets = this.buckets.map((le) => ({
789
- le,
790
- count: this.bucketCounts.get(le) || 0
791
- }));
792
- return {
793
- count: this.count,
794
- sum: this.sum,
795
- buckets
796
- };
797
- }
798
- /**
799
- * Get current observation count
800
- */
801
- getCount() {
802
- return this.count;
803
- }
804
- /**
805
- * Get sum of all observed values
806
- */
807
- getSum() {
808
- return this.sum;
809
- }
810
- /**
811
- * Get average of observed values
812
- */
813
- getAverage() {
814
- return this.count > 0 ? this.sum / this.count : 0;
815
- }
816
- /**
817
- * Get all labeled summaries
818
- * @returns Map of serialized label keys to histogram summaries
819
- */
820
- getLabeledSummaries() {
821
- const summaries = /* @__PURE__ */ new Map();
822
- for (const [labelKey, state] of this.labeledStates) {
823
- const buckets = this.buckets.map((le) => ({
824
- le,
825
- count: state.bucketCounts.get(le) || 0
826
- }));
827
- summaries.set(labelKey, {
828
- count: state.count,
829
- sum: state.sum,
830
- buckets
831
- });
832
- }
833
- return summaries;
834
- }
835
- /**
836
- * Get summary for specific label combination
837
- */
838
- getSummaryForLabels(labels) {
839
- const labelKey = this.serializeLabels(labels);
840
- const state = this.labeledStates.get(labelKey);
841
- if (!state) {
842
- return null;
843
- }
844
- const buckets = this.buckets.map((le) => ({
845
- le,
846
- count: state.bucketCounts.get(le) || 0
847
- }));
848
- return {
849
- count: state.count,
850
- sum: state.sum,
851
- buckets
852
- };
853
- }
854
- /**
855
- * Reset histogram to initial state (all label combinations)
856
- */
857
- reset() {
858
- this.count = 0;
859
- this.sum = 0;
860
- for (const bucket of this.buckets) {
861
- this.bucketCounts.set(bucket, 0);
862
- }
863
- this.labeledStates.clear();
864
- }
865
- /**
866
- * Serialize labels to deterministic string key
867
- * Format: key1=value1,key2=value2 (sorted by key)
868
- */
869
- serializeLabels(labels) {
870
- return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
871
- }
872
- };
873
- }
874
- });
875
-
876
- // src/telemetry/registry.ts
877
- var MetricsRegistry;
878
- var init_registry2 = __esm({
879
- "src/telemetry/registry.ts"() {
880
- init_counter();
881
- init_gauge();
882
- init_histogram();
883
- init_taxonomy();
884
- MetricsRegistry = class {
885
- counters = /* @__PURE__ */ new Map();
886
- gauges = /* @__PURE__ */ new Map();
887
- histograms = /* @__PURE__ */ new Map();
888
- /**
889
- * Get or create a counter
890
- *
891
- * @param name - Metric name from taxonomy
892
- * @returns Counter instance
893
- *
894
- * @example
895
- * ```typescript
896
- * const counter = registry.counter('schema_validations');
897
- * counter.inc();
898
- * ```
899
- */
900
- counter(name) {
901
- let counter = this.counters.get(name);
902
- if (!counter) {
903
- counter = new Counter(name);
904
- this.counters.set(name, counter);
905
- }
906
- return counter;
907
- }
908
- /**
909
- * Get or create a gauge
910
- *
911
- * @param name - Metric name from taxonomy
912
- * @returns Gauge instance
913
- *
914
- * @example
915
- * ```typescript
916
- * const gauge = registry.gauge('foundry_lookup_count');
917
- * gauge.set(42);
918
- * ```
919
- */
920
- gauge(name) {
921
- let gauge = this.gauges.get(name);
922
- if (!gauge) {
923
- gauge = new Gauge(name);
924
- this.gauges.set(name, gauge);
925
- }
926
- return gauge;
927
- }
928
- /**
929
- * Get or create a histogram
930
- *
931
- * @param name - Metric name from taxonomy
932
- * @param options - Optional histogram options
933
- * @returns Histogram instance
934
- *
935
- * @example
936
- * ```typescript
937
- * // Auto-applies ADR-0007 buckets for _ms metrics
938
- * const histogram = registry.histogram('config_load_ms');
939
- * histogram.observe(42.5);
940
- *
941
- * // Custom buckets
942
- * const custom = registry.histogram('custom_metric', {
943
- * buckets: [10, 50, 100, 500, 1000]
944
- * });
945
- * ```
946
- */
947
- histogram(name, options) {
948
- let histogram = this.histograms.get(name);
949
- if (!histogram) {
950
- histogram = new Histogram(name, options);
951
- this.histograms.set(name, histogram);
952
- }
953
- return histogram;
954
- }
955
- /**
956
- * Export all metrics as events
957
- *
958
- * Returns array of schema-compliant MetricsEvent objects.
959
- * Does not clear metrics (use flush() to clear after export).
960
- *
961
- * @returns Promise resolving to array of metrics events
962
- *
963
- * @example
964
- * ```typescript
965
- * const events = await registry.export();
966
- * console.log(JSON.stringify(events, null, 2));
967
- * ```
968
- */
969
- async export() {
970
- const events = [];
971
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
972
- for (const [name, counter] of this.counters) {
973
- const unit = await getDefaultUnit(name);
974
- events.push({
975
- timestamp,
976
- name,
977
- value: counter.getValue(),
978
- unit
979
- });
980
- for (const [labelKey, value] of counter.getLabeledValues()) {
981
- if (value > 0) {
982
- const tags = this.deserializeLabels(labelKey);
983
- events.push({
984
- timestamp,
985
- name,
986
- value,
987
- tags,
988
- unit
989
- });
990
- }
991
- }
992
- }
993
- for (const [name, gauge] of this.gauges) {
994
- const unit = await getDefaultUnit(name);
995
- events.push({
996
- timestamp,
997
- name,
998
- value: gauge.getValue(),
999
- unit
1000
- });
1001
- for (const [labelKey, value] of gauge.getLabeledValues()) {
1002
- const tags = this.deserializeLabels(labelKey);
1003
- events.push({
1004
- timestamp,
1005
- name,
1006
- value,
1007
- tags,
1008
- unit
1009
- });
1010
- }
1011
- }
1012
- for (const [name, histogram] of this.histograms) {
1013
- const unit = await getDefaultUnit(name);
1014
- events.push({
1015
- timestamp,
1016
- name,
1017
- value: histogram.getSummary(),
1018
- unit
1019
- });
1020
- for (const [labelKey, summary] of histogram.getLabeledSummaries()) {
1021
- if (summary.count > 0) {
1022
- const tags = this.deserializeLabels(labelKey);
1023
- events.push({
1024
- timestamp,
1025
- name,
1026
- value: summary,
1027
- tags,
1028
- unit
1029
- });
1030
- }
1031
- }
1032
- }
1033
- return events;
1034
- }
1035
- /**
1036
- * Deserialize label key back to tags object
1037
- * Format: key1=value1,key2=value2 → {key1: "value1", key2: "value2"}
1038
- */
1039
- deserializeLabels(labelKey) {
1040
- if (!labelKey) {
1041
- return {};
1042
- }
1043
- const tags = {};
1044
- for (const pair of labelKey.split(",")) {
1045
- const [key, value] = pair.split("=");
1046
- if (key && value) {
1047
- tags[key] = value;
1048
- }
1049
- }
1050
- return tags;
1051
- }
1052
- /**
1053
- * Export and clear all metrics
1054
- *
1055
- * Exports metrics as events, optionally emits them via logger,
1056
- * then resets all metrics to zero.
1057
- *
1058
- * @param options - Flush options
1059
- * @returns Promise resolving to array of exported events
1060
- *
1061
- * @example
1062
- * ```typescript
1063
- * // Export and clear
1064
- * const events = await registry.flush();
1065
- *
1066
- * // Export, emit to logger, and clear
1067
- * const events = await registry.flush({
1068
- * emit: (events) => console.log(JSON.stringify(events))
1069
- * });
1070
- * ```
1071
- */
1072
- async flush(options) {
1073
- const events = await this.export();
1074
- try {
1075
- if (options?.emit) {
1076
- options.emit(events);
1077
- }
1078
- } finally {
1079
- this.clear();
1080
- }
1081
- return events;
1082
- }
1083
- /**
1084
- * Clear all metrics (reset to zero)
1085
- *
1086
- * Resets all counters, gauges, and histograms to their initial state.
1087
- */
1088
- clear() {
1089
- for (const counter of this.counters.values()) {
1090
- counter.reset();
1091
- }
1092
- for (const gauge of this.gauges.values()) {
1093
- gauge.reset();
1094
- }
1095
- for (const histogram of this.histograms.values()) {
1096
- histogram.reset();
1097
- }
1098
- }
1099
- /**
1100
- * Get all registered metric names
1101
- *
1102
- * Returns array of all metric names that have been accessed
1103
- * (counters, gauges, or histograms).
1104
- */
1105
- getMetricNames() {
1106
- const names = /* @__PURE__ */ new Set();
1107
- for (const name of this.counters.keys()) {
1108
- names.add(name);
1109
- }
1110
- for (const name of this.gauges.keys()) {
1111
- names.add(name);
1112
- }
1113
- for (const name of this.histograms.keys()) {
1114
- names.add(name);
1115
- }
1116
- return Array.from(names);
1117
- }
1118
- /**
1119
- * Get total count of registered metrics
1120
- */
1121
- getMetricCount() {
1122
- return this.counters.size + this.gauges.size + this.histograms.size;
1123
- }
1124
- };
1125
- }
1126
- });
1127
-
1128
- // src/telemetry/types.ts
1129
- var init_types = __esm({
1130
- "src/telemetry/types.ts"() {
1131
- }
1132
- });
1133
-
1134
- // src/telemetry/validators.ts
1135
- var init_validators = __esm({
1136
- "src/telemetry/validators.ts"() {
1137
- init_schema();
1138
- }
1139
- });
1140
-
1141
- // src/telemetry/index.ts
1142
- var metrics;
1143
- var init_telemetry = __esm({
1144
- "src/telemetry/index.ts"() {
1145
- init_registry2();
1146
- init_registry2();
1147
- init_counter();
1148
- init_gauge();
1149
- init_histogram();
1150
- init_taxonomy();
1151
- init_types();
1152
- init_validators();
1153
- metrics = new MetricsRegistry();
1154
- }
1155
- });
1156
- async function loadMetaSchema(draft) {
1157
- const __filename = fileURLToPath(import.meta.url);
1158
- const __dirname2 = dirname(__filename);
1159
- const metaSchemaPath = join(
1160
- __dirname2,
1161
- "..",
1162
- "..",
1163
- "schemas",
1164
- "crucible-ts",
1165
- "meta",
1166
- draft,
1167
- "schema.json"
1168
- );
1169
- const content = await readFile(metaSchemaPath, "utf-8");
1170
- return JSON.parse(content);
1171
- }
1172
- async function loadVocabularySchemas(draft) {
1173
- if (draft !== "draft-2019-09" && draft !== "draft-2020-12") {
1174
- return [];
1175
- }
1176
- const __filename = fileURLToPath(import.meta.url);
1177
- const __dirname2 = dirname(__filename);
1178
- const vocabDir = join(__dirname2, "..", "..", "schemas", "crucible-ts", "meta", draft, "meta");
1179
- const vocabFiles = draft === "draft-2020-12" ? [
1180
- "core.json",
1181
- "applicator.json",
1182
- "unevaluated.json",
1183
- "validation.json",
1184
- "meta-data.json",
1185
- "format-annotation.json",
1186
- "content.json"
1187
- ] : [
1188
- "core.json",
1189
- "applicator.json",
1190
- "validation.json",
1191
- "meta-data.json",
1192
- "format.json",
1193
- "content.json"
1194
- ];
1195
- const schemas = [];
1196
- for (const file of vocabFiles) {
1197
- try {
1198
- const content = await readFile(join(vocabDir, file), "utf-8");
1199
- schemas.push(JSON.parse(content));
1200
- } catch {
1201
- }
1202
- }
1203
- return schemas;
1204
- }
1205
- async function loadReferencedSchema(uri) {
1206
- const __filename = fileURLToPath(import.meta.url);
1207
- const __dirname2 = dirname(__filename);
1208
- const repoRoot = join(__dirname2, "..", "..");
1209
- let resolvedPath;
1210
- if (uri.startsWith("https://schemas.fulmenhq.dev/")) {
1211
- let relativePath = uri.replace("https://schemas.fulmenhq.dev/", "");
1212
- if (relativePath.startsWith("crucible/")) {
1213
- relativePath = relativePath.slice("crucible/".length);
1214
- }
1215
- if (relativePath.startsWith("config/taxonomy/")) {
1216
- resolvedPath = join(
1217
- repoRoot,
1218
- "config",
1219
- "crucible-ts",
1220
- "taxonomy",
1221
- relativePath.split("/").pop() || ""
1222
- );
1223
- } else {
1224
- resolvedPath = join(repoRoot, "schemas", "crucible-ts", relativePath);
1225
- }
1226
- } else if (uri.startsWith("../../") || uri.startsWith("../")) {
1227
- const schemaBase = join(
1228
- repoRoot,
1229
- "schemas",
1230
- "crucible-ts",
1231
- "observability",
1232
- "metrics",
1233
- "v1.0.0"
1234
- );
1235
- resolvedPath = join(schemaBase, uri);
1236
- } else if (uri.startsWith("file://")) {
1237
- resolvedPath = fileURLToPath(uri);
1238
- } else {
1239
- throw new Error(`Cannot load remote schema: ${uri}`);
1240
- }
1241
- const content = await readFile(resolvedPath, "utf-8");
1242
- const ext = resolvedPath.split(".").pop()?.toLowerCase();
1243
- if (ext === "yaml" || ext === "yml") {
1244
- return parse(content);
1245
- }
1246
- return JSON.parse(content);
1247
- }
1248
- function detectDialect(schema) {
1249
- if (schema && typeof schema === "object" && !Array.isArray(schema)) {
1250
- const maybeSchema = schema;
1251
- const declared = maybeSchema.$schema;
1252
- if (typeof declared === "string") {
1253
- if (declared.includes("draft-04")) return "draft-04";
1254
- if (declared.includes("draft-06")) return "draft-06";
1255
- if (declared.includes("draft-07")) return "draft-07";
1256
- if (declared.includes("draft/2019-09")) return "draft-2019-09";
1257
- if (declared.includes("draft/2020-12")) return "draft-2020-12";
1258
- }
1259
- }
1260
- return "draft-2020-12";
1261
- }
1262
- function createAjv(dialect) {
1263
- const AjvCtor = dialect === "draft-2020-12" ? Ajv2020 : dialect === "draft-2019-09" ? Ajv2019 : dialect === "draft-04" ? AjvDraft04 : Ajv;
1264
- const ajv = new AjvCtor({
1265
- strict: false,
1266
- allErrors: true,
1267
- verbose: true,
1268
- // Allow schemas with $id to be added without replacing existing ones
1269
- addUsedSchema: false,
1270
- // draft-04 uses "id"; later drafts use "$id"
1271
- schemaId: dialect === "draft-04" ? "id" : "$id",
1272
- // Enable async schema loading for YAML references
1273
- loadSchema: loadReferencedSchema
1274
- });
1275
- applyFulmenAjvFormats(ajv);
1276
- return ajv;
1277
- }
1278
- async function getAjv(dialect) {
1279
- const existing = ajvInstances.get(dialect);
1280
- if (existing) {
1281
- const ready = metaschemaReady.get(dialect);
1282
- if (ready) await ready;
1283
- return existing;
1284
- }
1285
- const ajv = createAjv(dialect);
1286
- ajvInstances.set(dialect, ajv);
1287
- const readyPromise = Promise.all([loadVocabularySchemas(dialect), loadMetaSchema(dialect)]).then(([vocabSchemas, metaSchema]) => {
1288
- for (const vocabSchema of vocabSchemas) {
1289
- try {
1290
- ajv.addMetaSchema(vocabSchema);
1291
- } catch {
1292
- }
1293
- }
1294
- try {
1295
- ajv.addMetaSchema(metaSchema);
1296
- } catch {
1297
- }
1298
- }).catch((error) => {
1299
- throw new Error(`Failed to load metaschemas (${dialect}): ${error}`);
1300
- });
1301
- metaschemaReady.set(dialect, readyPromise);
1302
- await readyPromise;
1303
- return ajv;
1304
- }
1305
- async function compileSchema(schema, options = {}) {
1306
- const baseKey = typeof schema === "string" ? schema : JSON.stringify(schema);
1307
- let parsedSchema;
1308
- if (typeof schema === "string") {
1309
- try {
1310
- parsedSchema = JSON.parse(schema);
1311
- } catch {
1312
- parsedSchema = parse(schema);
1313
- }
1314
- } else if (Buffer.isBuffer(schema)) {
1315
- const content = schema.toString("utf-8");
1316
- try {
1317
- parsedSchema = JSON.parse(content);
1318
- } catch {
1319
- parsedSchema = parse(content);
1320
- }
1321
- } else {
1322
- parsedSchema = schema;
1323
- }
1324
- const dialect = detectDialect(parsedSchema);
1325
- const ajv = await getAjv(dialect);
1326
- const cacheKey = `${dialect}:${baseKey}`;
1327
- const cached = schemaCache.get(cacheKey);
1328
- if (cached !== void 0) {
1329
- return cached;
1330
- }
1331
- try {
1332
- if (options.aliases && options.aliases.length > 0) {
1333
- for (const alias of options.aliases) {
1334
- if (alias && ajv.getSchema(alias) === void 0) {
1335
- try {
1336
- if (typeof parsedSchema === "object" && parsedSchema !== null) {
1337
- ajv.addSchema(parsedSchema, alias);
1338
- }
1339
- } catch {
1340
- }
1341
- }
1342
- }
1343
- }
1344
- const validator = typeof parsedSchema === "boolean" ? ajv.compile(parsedSchema) : await ajv.compileAsync(parsedSchema);
1345
- schemaCache.set(cacheKey, validator);
1346
- return validator;
1347
- } catch (error) {
1348
- throw SchemaValidationError.parseFailed(
1349
- {
1350
- type: "string",
1351
- content: typeof schema === "string" ? schema : JSON.stringify(schema)
1352
- },
1353
- error
1354
- );
1355
- }
1356
- }
1357
- function validateData(data, validator) {
1358
- const valid = validator(data);
1359
- const result = {
1360
- valid,
1361
- diagnostics: [],
1362
- source: "ajv"
1363
- };
1364
- if (!valid && validator.errors) {
1365
- const errors = validator.errors;
1366
- if (Array.isArray(errors)) {
1367
- result.diagnostics = errors.map(
1368
- (error) => createDiagnostic(
1369
- error.instancePath || "",
1370
- error.message || "Validation failed",
1371
- error.keyword || "unknown",
1372
- "ERROR",
1373
- "ajv"
1374
- )
1375
- );
1376
- }
1377
- metrics.counter("schema_validation_errors").inc();
1378
- } else {
1379
- metrics.counter("schema_validations").inc();
1380
- }
1381
- return result;
1382
- }
1383
- async function compileSchemaById(schemaId, registryOptions) {
1384
- try {
1385
- const registry = getSchemaRegistry(registryOptions);
1386
- const metadata = await registry.getSchema(schemaId);
1387
- const content = await readFile(metadata.path, "utf-8");
1388
- const aliases = [];
1389
- const normalizedRelativePath = metadata.relativePath.replace(/\\/g, "/");
1390
- if (normalizedRelativePath) {
1391
- aliases.push(
1392
- new URL(`crucible/${normalizedRelativePath}`, "https://schemas.fulmenhq.dev/").toString()
1393
- );
1394
- }
1395
- return compileSchema(content, { aliases });
1396
- } catch (error) {
1397
- metrics.counter("schema_validation_errors").inc();
1398
- throw error;
1399
- }
1400
- }
1401
- async function validateDataBySchemaId(data, schemaId, registryOptions) {
1402
- try {
1403
- const validator = await compileSchemaById(schemaId, registryOptions);
1404
- return validateData(data, validator);
1405
- } catch (error) {
1406
- metrics.counter("schema_validation_errors").inc();
1407
- throw error;
1408
- }
1409
- }
1410
- var ajvInstances, metaschemaReady, schemaCache;
1411
- var init_validator = __esm({
1412
- "src/schema/validator.ts"() {
1413
- init_telemetry();
1414
- init_ajv_formats();
1415
- init_errors();
1416
- init_registry();
1417
- init_utils();
1418
- ajvInstances = /* @__PURE__ */ new Map();
1419
- metaschemaReady = /* @__PURE__ */ new Map();
1420
- schemaCache = /* @__PURE__ */ new Map();
1421
- }
1422
- });
1423
- var init_cli = __esm({
1424
- "src/schema/cli.ts"() {
1425
- init_goneat_bridge();
1426
- init_normalizer();
1427
- init_registry();
1428
- init_utils();
1429
- init_validator();
1430
- }
1431
- });
1432
- var init_export = __esm({
1433
- "src/schema/export.ts"() {
1434
- init_errors();
1435
- init_registry();
1436
- init_validator();
1437
- }
1438
- });
1439
-
1440
- // src/schema/index.ts
1441
- var init_schema = __esm({
1442
- "src/schema/index.ts"() {
1443
- init_ajv_formats();
1444
- init_cli();
1445
- init_errors();
1446
- init_export();
1447
- init_goneat_bridge();
1448
- init_normalizer();
1449
- init_registry();
1450
- init_utils();
1451
- init_validator();
1452
- }
1453
- });
1454
- var init_correlation = __esm({
1455
- "src/errors/correlation.ts"() {
1456
- }
1457
- });
1458
-
1459
- // src/errors/severity.ts
1460
- function getDefaultSeverity() {
1461
- return {
1462
- name: Severity.MEDIUM,
1463
- level: 2
1464
- };
1465
- }
1466
- var Severity, SEVERITY_LEVELS;
1467
- var init_severity = __esm({
1468
- "src/errors/severity.ts"() {
1469
- Severity = {
1470
- INFO: "info",
1471
- LOW: "low",
1472
- MEDIUM: "medium",
1473
- HIGH: "high",
1474
- CRITICAL: "critical"
1475
- };
1476
- SEVERITY_LEVELS = {
1477
- info: 0,
1478
- low: 1,
1479
- medium: 2,
1480
- high: 3,
1481
- critical: 4
1482
- };
1483
- }
1484
- });
1485
-
1486
- // src/errors/serialization.ts
1487
- function extractErrorMessage(error) {
1488
- if (error instanceof Error) {
1489
- return error.message;
1490
- }
1491
- if (isErrorLike(error)) {
1492
- return error.message;
1493
- }
1494
- if (typeof error === "string") {
1495
- return error;
1496
- }
1497
- return String(error);
1498
- }
1499
- function extractStackTrace(error) {
1500
- if (error instanceof Error) {
1501
- return error.stack;
1502
- }
1503
- if (isErrorLike(error) && typeof error.stack === "string") {
1504
- return error.stack;
1505
- }
1506
- return void 0;
1507
- }
1508
- function isErrorLike(value) {
1509
- return typeof value === "object" && value !== null && "message" in value && typeof value.message === "string";
1510
- }
1511
- var init_serialization = __esm({
1512
- "src/errors/serialization.ts"() {
1513
- init_severity();
1514
- }
1515
- });
1516
-
1517
- // src/errors/validators.ts
1518
- async function validateErrorData(data) {
1519
- return ErrorValidator.getInstance().validate(data);
1520
- }
1521
- var ErrorValidator;
1522
- var init_validators2 = __esm({
1523
- "src/errors/validators.ts"() {
1524
- init_schema();
1525
- ErrorValidator = class _ErrorValidator {
1526
- static instance;
1527
- validateFn = null;
1528
- initPromise = null;
1529
- initError = null;
1530
- constructor() {
1531
- }
1532
- /**
1533
- * Get singleton instance
1534
- */
1535
- static getInstance() {
1536
- if (!_ErrorValidator.instance) {
1537
- _ErrorValidator.instance = new _ErrorValidator();
1538
- }
1539
- return _ErrorValidator.instance;
1540
- }
1541
- /**
1542
- * Initialize validator (lazy load, async)
1543
- */
1544
- async init() {
1545
- if (this.validateFn !== null || this.initError !== null) {
1546
- return;
1547
- }
1548
- if (this.initPromise) {
1549
- return this.initPromise;
1550
- }
1551
- this.initPromise = (async () => {
1552
- try {
1553
- await compileSchemaById("pathfinder/v1.0.0/error-response");
1554
- await compileSchemaById("assessment/v1.0.0/severity-definitions");
1555
- this.validateFn = await compileSchemaById("error-handling/v1.0.0/error-response");
1556
- } catch (err) {
1557
- this.initError = err instanceof Error ? err : new Error(String(err));
1558
- throw new Error(`Failed to initialize error validator: ${this.initError.message}`);
1559
- }
1560
- })();
1561
- return this.initPromise;
1562
- }
1563
- /**
1564
- * Validate error data against schema
1565
- *
1566
- * @param data - Data to validate
1567
- * @returns Promise resolving to true if valid, false otherwise
1568
- * @throws {Error} If validator failed to initialize
1569
- */
1570
- async validate(data) {
1571
- if (this.validateFn === null) {
1572
- await this.init();
1573
- }
1574
- if (this.initError) {
1575
- throw this.initError;
1576
- }
1577
- if (!this.validateFn) {
1578
- throw new Error("Validator not initialized");
1579
- }
1580
- return this.validateFn(data);
1581
- }
1582
- /**
1583
- * Get validation errors from last validation
1584
- *
1585
- * @returns Validation errors or null
1586
- */
1587
- getErrors() {
1588
- if (!this.validateFn) {
1589
- return null;
1590
- }
1591
- return this.validateFn.errors;
1592
- }
1593
- /**
1594
- * Reset validator state (for testing)
1595
- * @internal
1596
- */
1597
- static _reset() {
1598
- _ErrorValidator.instance = new _ErrorValidator();
1599
- }
1600
- };
1601
- }
1602
- });
1603
-
1604
- // src/errors/fulmen-error.ts
1605
- function isFulmenErrorData(value) {
1606
- return typeof value === "object" && value !== null && "code" in value && typeof value.code === "string" && "message" in value && typeof value.message === "string";
1607
- }
1608
- var FulmenError;
1609
- var init_fulmen_error = __esm({
1610
- "src/errors/fulmen-error.ts"() {
1611
- init_serialization();
1612
- init_severity();
1613
- init_validators2();
1614
- FulmenError = class _FulmenError extends Error {
1615
- data;
1616
- constructor(data) {
1617
- super(data.message);
1618
- this.name = "FulmenError";
1619
- this.data = Object.freeze({ ...data });
1620
- Error.captureStackTrace(this, _FulmenError);
1621
- }
1622
- /**
1623
- * Serialize to JSON (schema-compliant)
1624
- */
1625
- toJSON() {
1626
- return this.data;
1627
- }
1628
- /**
1629
- * Check equality with another FulmenError
1630
- */
1631
- equals(other) {
1632
- return JSON.stringify(this.data) === JSON.stringify(other.data);
1633
- }
1634
- /**
1635
- * Get severity level for comparison
1636
- */
1637
- getSeverityLevel() {
1638
- return this.data.severity_level ?? SEVERITY_LEVELS[this.data.severity ?? "medium"];
1639
- }
1640
- /**
1641
- * Wrap an existing error with FulmenError structure
1642
- *
1643
- * @param error - Error to wrap (Error instance or FulmenErrorData)
1644
- * @param options - Additional error options
1645
- * @returns New FulmenError instance
1646
- *
1647
- * @example
1648
- * ```typescript
1649
- * try {
1650
- * throw new Error('Config invalid');
1651
- * } catch (err) {
1652
- * const fulmenErr = FulmenError.wrap(err, {
1653
- * code: 'CONFIG_INVALID',
1654
- * severity: 'high',
1655
- * exit_code: 2
1656
- * });
1657
- * throw fulmenErr;
1658
- * }
1659
- * ```
1660
- */
1661
- static wrap(error, options = {}) {
1662
- if (error instanceof _FulmenError) {
1663
- const effectiveSeverity = options.severity ?? error.data.severity ?? Severity.MEDIUM;
1664
- const effectiveSeverityLevel = SEVERITY_LEVELS[effectiveSeverity];
1665
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1666
- return new _FulmenError({
1667
- ...error.data,
1668
- ...options,
1669
- code: options.code ?? error.data.code,
1670
- message: error.data.message,
1671
- severity: effectiveSeverity,
1672
- // Consistent severity
1673
- severity_level: effectiveSeverityLevel,
1674
- // Recomputed level
1675
- timestamp
1676
- // Updated timestamp
1677
- });
1678
- }
1679
- if (isFulmenErrorData(error)) {
1680
- const defaultSeverity = getDefaultSeverity();
1681
- const effectiveSeverity = options.severity ?? error.severity ?? defaultSeverity.name;
1682
- const effectiveSeverityLevel = SEVERITY_LEVELS[effectiveSeverity];
1683
- return new _FulmenError({
1684
- ...error,
1685
- ...options,
1686
- severity: effectiveSeverity,
1687
- severity_level: effectiveSeverityLevel,
1688
- // Recomputed, not from error.severity_level
1689
- timestamp: error.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
1690
- });
1691
- }
1692
- return _FulmenError.fromError(error, options);
1693
- }
1694
- /**
1695
- * Create FulmenError from native Error object
1696
- *
1697
- * @param err - Native Error instance
1698
- * @param options - Error options
1699
- * @returns New FulmenError instance
1700
- *
1701
- * @example
1702
- * ```typescript
1703
- * const err = new TypeError('Invalid type');
1704
- * const fulmenErr = FulmenError.fromError(err, {
1705
- * code: 'TYPE_ERROR',
1706
- * severity: 'medium'
1707
- * });
1708
- * ```
1709
- */
1710
- static fromError(err, options = {}) {
1711
- const code = options.code ?? "UNKNOWN_ERROR";
1712
- const severity = options.severity ?? Severity.MEDIUM;
1713
- const severityLevel = SEVERITY_LEVELS[severity];
1714
- const message = extractErrorMessage(err);
1715
- const stack = extractStackTrace(err);
1716
- const data = {
1717
- code,
1718
- message,
1719
- severity,
1720
- severity_level: severityLevel,
1721
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1722
- ...options,
1723
- context: {
1724
- ...options.context,
1725
- originalName: err instanceof Error ? err.name : typeof err,
1726
- stack
1727
- },
1728
- original: stack || message
1729
- };
1730
- return new _FulmenError(data);
1731
- }
1732
- /**
1733
- * Validate error data against schema
1734
- *
1735
- * @param data - Error data to validate
1736
- * @returns Promise resolving to true if valid
1737
- *
1738
- * @example
1739
- * ```typescript
1740
- * const data = { code: 'TEST', message: 'Test error' };
1741
- * if (await FulmenError.validate(data)) {
1742
- * const err = new FulmenError(data);
1743
- * }
1744
- * ```
1745
- */
1746
- static async validate(data) {
1747
- return validateErrorData(data);
1748
- }
1749
- /**
1750
- * Exit process with structured error
1751
- *
1752
- * Logs error as JSON and exits with specified exit code.
1753
- * Mockable for testing (override process.exit).
1754
- *
1755
- * @param error - FulmenError instance
1756
- * @param options - Exit options
1757
- *
1758
- * @example
1759
- * ```typescript
1760
- * const err = FulmenError.fromError(new Error('Fatal'), {
1761
- * code: 'FATAL_ERROR',
1762
- * exit_code: 1
1763
- * });
1764
- * FulmenError.exitWithError(err); // Exits with code 1
1765
- * ```
1766
- */
1767
- static exitWithError(error, options = {}) {
1768
- const logger = options.logger ?? console.error;
1769
- const exitCode = error.data.exit_code ?? 1;
1770
- logger(JSON.stringify(error.toJSON(), null, 2));
1771
- process.exit(exitCode);
1772
- }
1773
- };
1774
- }
1775
- });
1776
-
1777
- // src/errors/index.ts
1778
- var init_errors2 = __esm({
1779
- "src/errors/index.ts"() {
1780
- init_correlation();
1781
- init_fulmen_error();
1782
- init_serialization();
1783
- init_severity();
1784
- init_validators2();
1785
- }
1786
- });
1787
-
1788
- // src/appidentity/errors.ts
1789
- var AppIdentityError;
1790
- var init_errors3 = __esm({
1791
- "src/appidentity/errors.ts"() {
1792
- init_errors2();
1793
- AppIdentityError = class _AppIdentityError extends FulmenError {
1794
- identityPath;
1795
- constructor(message, identityPath, cause) {
1796
- let errorData;
1797
- if (cause) {
1798
- errorData = FulmenError.fromError(cause, {
1799
- code: "APP_IDENTITY_ERROR",
1800
- severity: "high",
1801
- context: { identityPath }
1802
- }).data;
1803
- } else {
1804
- errorData = {
1805
- code: "APP_IDENTITY_ERROR",
1806
- message,
1807
- severity: "high",
1808
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1809
- context: { identityPath }
1810
- };
1811
- }
1812
- super(errorData);
1813
- this.name = "AppIdentityError";
1814
- this.identityPath = identityPath;
1815
- if (Error.captureStackTrace) {
1816
- Error.captureStackTrace(this, _AppIdentityError);
1817
- }
1818
- }
1819
- /**
1820
- * Create error for identity not found
1821
- */
1822
- static notFound(searchedPaths) {
1823
- const message = `App identity not found
1824
- Searched paths:
1825
- ${searchedPaths.map((p) => ` - ${p}`).join("\n")}`;
1826
- return new _AppIdentityError(message);
1827
- }
1828
- /**
1829
- * Create error for schema validation failure
1830
- */
1831
- static validationFailed(path, diagnostics) {
1832
- const errorCount = diagnostics.filter((d) => d.severity === "ERROR").length;
1833
- const warningCount = diagnostics.filter((d) => d.severity === "WARN").length;
1834
- let message = `Invalid app identity: ${path}
1835
- `;
1836
- message += `Validation errors: ${errorCount} error(s), ${warningCount} warning(s)
1837
- `;
1838
- const displayDiagnostics = diagnostics.slice(0, 3);
1839
- for (const diag of displayDiagnostics) {
1840
- message += ` - ${diag.message}`;
1841
- if (diag.pointer) {
1842
- message += ` at ${diag.pointer}`;
1843
- }
1844
- message += "\n";
1845
- }
1846
- if (diagnostics.length > 3) {
1847
- message += ` ... and ${diagnostics.length - 3} more
1848
- `;
1849
- }
1850
- return new _AppIdentityError(message, path);
1851
- }
1852
- /**
1853
- * Create error for environment variable override pointing to missing file
1854
- */
1855
- static envOverrideMissing(envPath) {
1856
- const message = `FULMEN_APP_IDENTITY_PATH points to missing file: ${envPath}
1857
- `;
1858
- return new _AppIdentityError(message, envPath);
1859
- }
1860
- /**
1861
- * Create error for YAML parsing failure
1862
- */
1863
- static parseFailed(path, cause) {
1864
- const message = `Failed to parse identity file: ${path}
1865
- ${cause.message}`;
1866
- return new _AppIdentityError(message, path, cause);
1867
- }
1868
- /**
1869
- * Create error for file read failure
1870
- */
1871
- static readFailed(path, cause) {
1872
- const message = `Failed to read identity file: ${path}
1873
- ${cause.message}`;
1874
- return new _AppIdentityError(message, path, cause);
1875
- }
1876
- /**
1877
- * Create error for embedded identity already registered
1878
- *
1879
- * Uses first-wins semantics - once registered, cannot be replaced
1880
- */
1881
- static alreadyRegistered() {
1882
- const message = "Embedded identity already registered. Registration uses first-wins semantics and cannot be replaced.";
1883
- return new _AppIdentityError(message);
1884
- }
1885
- /**
1886
- * Create error for embedded identity YAML parsing failure
1887
- */
1888
- static embeddedParseFailed(cause) {
1889
- const message = `Failed to parse embedded identity YAML: ${cause.message}`;
1890
- return new _AppIdentityError(message, void 0, cause);
1891
- }
1892
- /**
1893
- * Create error for embedded identity schema validation failure
1894
- */
1895
- static embeddedValidationFailed(diagnostics) {
1896
- const errorCount = diagnostics.filter((d) => d.severity === "ERROR").length;
1897
- const warningCount = diagnostics.filter((d) => d.severity === "WARN").length;
1898
- let message = "Invalid embedded identity\n";
1899
- message += `Validation errors: ${errorCount} error(s), ${warningCount} warning(s)
1900
- `;
1901
- const displayDiagnostics = diagnostics.slice(0, 3);
1902
- for (const diag of displayDiagnostics) {
1903
- message += ` - ${diag.message}`;
1904
- if (diag.pointer) {
1905
- message += ` at ${diag.pointer}`;
1906
- }
1907
- message += "\n";
1908
- }
1909
- if (diagnostics.length > 3) {
1910
- message += ` ... and ${diagnostics.length - 3} more
1911
- `;
1912
- }
1913
- return new _AppIdentityError(message);
1914
- }
1915
- };
1916
- }
1917
- });
1918
- function deepFreeze(obj) {
1919
- Object.freeze(obj);
1920
- Object.getOwnPropertyNames(obj).forEach((prop) => {
1921
- const value = obj[prop];
1922
- if (value !== null && (typeof value === "object" || typeof value === "function") && !Object.isFrozen(value)) {
1923
- deepFreeze(value);
1924
- }
1925
- });
1926
- return obj;
1927
- }
1928
- async function registerEmbeddedIdentity(data, options) {
1929
- if (isRegistered) {
1930
- throw AppIdentityError.alreadyRegistered();
1931
- }
1932
- const skipValidation = options?.skipValidation ?? false;
1933
- let identity;
1934
- if (typeof data === "string") {
1935
- let parsed;
1936
- try {
1937
- parsed = parse(data);
1938
- } catch (error) {
1939
- throw AppIdentityError.embeddedParseFailed(
1940
- error instanceof Error ? error : new Error(String(error))
1941
- );
1942
- }
1943
- if (!skipValidation) {
1944
- const result = await validateDataBySchemaId(parsed, APP_IDENTITY_SCHEMA_ID);
1945
- if (!result.valid) {
1946
- throw AppIdentityError.embeddedValidationFailed(result.diagnostics);
1947
- }
1948
- }
1949
- identity = parsed;
1950
- } else {
1951
- if (!skipValidation) {
1952
- const result = await validateDataBySchemaId(data, APP_IDENTITY_SCHEMA_ID);
1953
- if (!result.valid) {
1954
- throw AppIdentityError.embeddedValidationFailed(result.diagnostics);
1955
- }
1956
- }
1957
- identity = data;
1958
- }
1959
- embeddedIdentity = deepFreeze(structuredClone(identity));
1960
- isRegistered = true;
1961
- }
1962
- function hasEmbeddedIdentity() {
1963
- return isRegistered;
1964
- }
1965
- function getEmbeddedIdentity() {
1966
- return embeddedIdentity;
1967
- }
1968
- function clearEmbeddedIdentity() {
1969
- embeddedIdentity = null;
1970
- isRegistered = false;
1971
- }
1972
- var embeddedIdentity, isRegistered;
1973
- var init_embedded = __esm({
1974
- "src/appidentity/embedded.ts"() {
1975
- init_schema();
1976
- init_constants();
1977
- init_errors3();
1978
- embeddedIdentity = null;
1979
- isRegistered = false;
1980
- }
1981
- });
1982
-
1983
- // src/appidentity/cache.ts
1984
- function getCachedIdentity() {
1985
- return cachedIdentity;
1986
- }
1987
- function setCachedIdentity(identity) {
1988
- cachedIdentity = identity;
1989
- }
1990
- function clearIdentityCache() {
1991
- cachedIdentity = null;
1992
- }
1993
- var cachedIdentity;
1994
- var init_cache = __esm({
1995
- "src/appidentity/cache.ts"() {
1996
- cachedIdentity = null;
1997
- }
1998
- });
1999
- async function discoverIdentityPath(options) {
2000
- if (options?.path) {
2001
- const exists = await fileExists(options.path);
2002
- if (!exists) {
2003
- throw AppIdentityError.notFound([options.path]);
2004
- }
2005
- return { path: options.path, source: "explicit" };
2006
- }
2007
- const envPath = process.env[APP_IDENTITY_ENV_VAR];
2008
- if (envPath) {
2009
- const exists = await fileExists(envPath);
2010
- if (!exists) {
2011
- throw AppIdentityError.envOverrideMissing(envPath);
2012
- }
2013
- return { path: envPath, source: "env" };
2014
- }
2015
- const startDir = options?.startDir || process.cwd();
2016
- const result = await searchAncestors(startDir);
2017
- if (result) {
2018
- return { path: result, source: "ancestor" };
2019
- }
2020
- return null;
2021
- }
2022
- async function searchAncestors(startDir) {
2023
- let currentDir = startDir;
2024
- const searchedPaths = [];
2025
- for (let i = 0; i < MAX_ANCESTOR_SEARCH_DEPTH; i++) {
2026
- const candidatePath = join(currentDir, APP_IDENTITY_DIR, APP_IDENTITY_FILENAME);
2027
- searchedPaths.push(candidatePath);
2028
- if (await fileExists(candidatePath)) {
2029
- return candidatePath;
2030
- }
2031
- const parentDir = dirname(currentDir);
2032
- if (parentDir === currentDir) {
2033
- throw AppIdentityError.notFound(searchedPaths);
2034
- }
2035
- currentDir = parentDir;
2036
- }
2037
- throw AppIdentityError.notFound(searchedPaths);
2038
- }
2039
- async function fileExists(path) {
2040
- try {
2041
- await access(path);
2042
- return true;
2043
- } catch {
2044
- return false;
2045
- }
2046
- }
2047
- var init_discovery = __esm({
2048
- "src/appidentity/discovery.ts"() {
2049
- init_constants();
2050
- init_errors3();
2051
- }
2052
- });
2053
- function deepFreeze2(obj) {
2054
- Object.freeze(obj);
2055
- Object.getOwnPropertyNames(obj).forEach((prop) => {
2056
- const value = obj[prop];
2057
- if (value !== null && (typeof value === "object" || typeof value === "function") && !Object.isFrozen(value)) {
2058
- deepFreeze2(value);
2059
- }
2060
- });
2061
- return obj;
2062
- }
2063
- async function loadIdentity(options) {
2064
- if (options?.identity) {
2065
- return deepFreeze2(structuredClone(options.identity));
2066
- }
2067
- if (!options?.skipCache) {
2068
- const cached = getCachedIdentity();
2069
- if (cached) {
2070
- return cached;
2071
- }
2072
- }
2073
- let discovery;
2074
- try {
2075
- discovery = await discoverIdentityPath({
2076
- path: options?.path,
2077
- startDir: options?.startDir
2078
- });
2079
- } catch (error) {
2080
- const hasExplicitPath = Boolean(options?.path);
2081
- const hasEnvOverride = Boolean(process.env[APP_IDENTITY_ENV_VAR]);
2082
- if (!hasExplicitPath && !hasEnvOverride && error instanceof AppIdentityError) {
2083
- const embedded = getEmbeddedIdentity();
2084
- if (embedded) {
2085
- setCachedIdentity(embedded);
2086
- return embedded;
2087
- }
2088
- }
2089
- throw error;
2090
- }
2091
- if (!discovery) {
2092
- const embedded = getEmbeddedIdentity();
2093
- if (embedded) {
2094
- setCachedIdentity(embedded);
2095
- return embedded;
2096
- }
2097
- throw AppIdentityError.notFound([]);
2098
- }
2099
- let content;
2100
- try {
2101
- content = await readFile(discovery.path, "utf-8");
2102
- } catch (error) {
2103
- throw AppIdentityError.readFailed(
2104
- discovery.path,
2105
- error instanceof Error ? error : new Error(String(error))
2106
- );
2107
- }
2108
- let parsed;
2109
- try {
2110
- parsed = parse(content);
2111
- } catch (error) {
2112
- throw AppIdentityError.parseFailed(
2113
- discovery.path,
2114
- error instanceof Error ? error : new Error(String(error))
2115
- );
2116
- }
2117
- if (!options?.skipValidation) {
2118
- const result = await validateDataBySchemaId(parsed, APP_IDENTITY_SCHEMA_ID);
2119
- if (!result.valid) {
2120
- throw AppIdentityError.validationFailed(discovery.path, result.diagnostics);
2121
- }
2122
- }
2123
- const identity = deepFreeze2(structuredClone(parsed));
2124
- setCachedIdentity(identity);
2125
- return identity;
2126
- }
2127
- var init_loader = __esm({
2128
- "src/appidentity/loader.ts"() {
2129
- init_schema();
2130
- init_cache();
2131
- init_constants();
2132
- init_discovery();
2133
- init_embedded();
2134
- init_errors3();
2135
- }
2136
- });
2137
-
2138
- // src/appidentity/runtime.ts
2139
- function detectRuntime() {
2140
- const versions = process.versions;
2141
- if (typeof versions.bun === "string" && versions.bun.length > 0) {
2142
- return { name: "bun", version: versions.bun };
2143
- }
2144
- if (typeof versions.node === "string" && versions.node.length > 0) {
2145
- return { name: "node", version: versions.node };
2146
- }
2147
- return { name: "unknown" };
2148
- }
2149
- function buildRuntimeInfo(options = {}) {
2150
- const runtime = detectRuntime();
2151
- const serviceName = options.serviceName ?? options.identity?.app.binary_name ?? "unknown-service";
2152
- const vendor = options.vendor ?? options.identity?.app.vendor;
2153
- return {
2154
- service: {
2155
- name: serviceName,
2156
- vendor,
2157
- version: options.version
2158
- },
2159
- runtime,
2160
- platform: {
2161
- os: process.platform,
2162
- arch: process.arch
2163
- }
2164
- };
2165
- }
2166
- var init_runtime = __esm({
2167
- "src/appidentity/runtime.ts"() {
2168
- }
2169
- });
2170
-
2171
- // src/appidentity/index.ts
2172
- init_constants();
2173
- init_embedded();
2174
- init_errors3();
2175
-
2176
- // src/appidentity/helpers.ts
2177
- init_loader();
2178
- async function getBinaryName(options) {
2179
- const identity = await loadIdentity(options);
2180
- return identity.app.binary_name;
2181
- }
2182
- async function getVendor(options) {
2183
- const identity = await loadIdentity(options);
2184
- return identity.app.vendor;
2185
- }
2186
- async function getEnvPrefix(options) {
2187
- const identity = await loadIdentity(options);
2188
- return identity.app.env_prefix;
2189
- }
2190
- async function getConfigName(options) {
2191
- const identity = await loadIdentity(options);
2192
- return identity.app.config_name;
2193
- }
2194
- async function getTelemetryNamespace(options) {
2195
- const identity = await loadIdentity(options);
2196
- return identity.metadata?.telemetry_namespace ?? identity.app.binary_name;
2197
- }
2198
- async function getConfigIdentifiers(options) {
2199
- const identity = await loadIdentity(options);
2200
- return Object.freeze({
2201
- vendor: identity.app.vendor,
2202
- configName: identity.app.config_name
2203
- });
2204
- }
2205
- async function buildEnvVar(key, options) {
2206
- const envPrefix = await getEnvPrefix(options);
2207
- const normalizedKey = key.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
2208
- return `${envPrefix}${normalizedKey}`;
2209
- }
2210
- async function getEnvVar(key, options) {
2211
- const envVarName = await buildEnvVar(key, options);
2212
- return process.env[envVarName];
2213
- }
2214
-
2215
- // src/appidentity/index.ts
2216
- init_loader();
2217
- init_runtime();
2218
-
2219
- // src/docscribe/normalize.ts
2220
- var decoder = new TextDecoder("utf-8", { fatal: false, ignoreBOM: true });
2221
- function stripBom(value) {
2222
- return value.charCodeAt(0) === 65279 ? value.slice(1) : value;
2223
- }
2224
- function normalizeNewlines(value) {
2225
- return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2226
- }
2227
- function normalizeInput(input) {
2228
- let content;
2229
- if (typeof input === "string") {
2230
- content = input;
2231
- } else if (input instanceof Uint8Array) {
2232
- content = decoder.decode(input);
2233
- } else {
2234
- content = decoder.decode(new Uint8Array(input));
2235
- }
2236
- const normalized = normalizeNewlines(stripBom(content));
2237
- return { content: normalized, original: input };
2238
- }
2239
-
2240
- // src/docscribe/types.ts
2241
- var DocScribeError = class extends Error {
2242
- constructor(message, options) {
2243
- super(message, options);
2244
- Object.setPrototypeOf(this, new.target.prototype);
2245
- this.name = "DocScribeError";
2246
- }
2247
- };
2248
- var DocScribeParseError = class extends DocScribeError {
2249
- constructor(message, options) {
2250
- super(message, options);
2251
- Object.setPrototypeOf(this, new.target.prototype);
2252
- this.name = "DocScribeParseError";
2253
- }
2254
- };
2255
- var DocScribeUnsupportedFormatError = class extends DocScribeError {
2256
- constructor(message, options) {
2257
- super(message, options);
2258
- Object.setPrototypeOf(this, new.target.prototype);
2259
- this.name = "DocScribeUnsupportedFormatError";
2260
- }
2261
- };
2262
-
2263
- // src/docscribe/frontmatter.ts
2264
- var FRONTMATTER_DELIMITER = "---";
2265
- var STRING_KEYS = /* @__PURE__ */ new Set([
2266
- "title",
2267
- "description",
2268
- "author",
2269
- "date",
2270
- "lastUpdated",
2271
- "status",
2272
- "version"
2273
- ]);
2274
- function parseFrontmatter(input) {
2275
- const { content } = normalizeInput(input);
2276
- const bounds = locateFrontmatter(content);
2277
- if (!bounds) {
2278
- return { metadata: null, body: content, range: null, bodyStartLine: 1 };
2279
- }
2280
- const yamlBlock = content.slice(bounds.openEnd, bounds.closingStart);
2281
- const metadata = yamlBlock.trim().length === 0 ? {} : parseYamlMetadata(yamlBlock);
2282
- const bodyRaw = content.slice(bounds.closingEnd);
2283
- const leadingNewlinesMatch = bodyRaw.match(/^\n+/);
2284
- const leadingNewlinesLength = leadingNewlinesMatch ? leadingNewlinesMatch[0].length : 0;
2285
- const bodyStartIndex = bounds.closingEnd + leadingNewlinesLength;
2286
- const prefix = content.slice(0, bodyStartIndex);
2287
- const bodyStartLine = prefix.length === 0 ? 1 : prefix.split("\n").length;
2288
- const body = bodyRaw.slice(leadingNewlinesLength);
2289
- return {
2290
- metadata,
2291
- body,
2292
- range: { start: bounds.start, end: bounds.closingEnd },
2293
- bodyStartLine
2294
- };
2295
- }
2296
- function stripFrontmatter(input) {
2297
- return parseFrontmatter(input).body;
2298
- }
2299
- function extractMetadata(input) {
2300
- return parseFrontmatter(input).metadata;
2301
- }
2302
- function locateFrontmatter(content) {
2303
- let cursor = 0;
2304
- while (cursor < content.length) {
2305
- const newlineIndex = content.indexOf("\n", cursor);
2306
- const lineEnd = newlineIndex === -1 ? content.length : newlineIndex;
2307
- const line = content.slice(cursor, lineEnd);
2308
- if (line.trim().length === 0) {
2309
- if (newlineIndex === -1) {
2310
- return null;
2311
- }
2312
- cursor = newlineIndex + 1;
2313
- continue;
2314
- }
2315
- if (line.trim() !== FRONTMATTER_DELIMITER) {
2316
- return null;
2317
- }
2318
- const openStart = cursor;
2319
- const openEnd = newlineIndex === -1 ? content.length : newlineIndex + 1;
2320
- let search = openEnd;
2321
- while (search <= content.length) {
2322
- const nextNewline = content.indexOf("\n", search);
2323
- const segmentEnd = nextNewline === -1 ? content.length : nextNewline;
2324
- const candidate = content.slice(search, segmentEnd).trim();
2325
- if (candidate === FRONTMATTER_DELIMITER) {
2326
- const closingStart = search;
2327
- const closingEnd = nextNewline === -1 ? segmentEnd : nextNewline + 1;
2328
- return {
2329
- start: openStart,
2330
- openEnd,
2331
- closingStart,
2332
- closingEnd
2333
- };
2334
- }
2335
- if (nextNewline === -1) {
2336
- break;
2337
- }
2338
- search = nextNewline + 1;
2339
- }
2340
- return null;
2341
- }
2342
- return null;
2343
- }
2344
- function parseYamlMetadata(source) {
2345
- const document = parseDocument(source, { prettyErrors: true });
2346
- if (document.errors.length > 0) {
2347
- const error = document.errors[0];
2348
- const location = error.linePos && error.linePos.length > 0 ? error.linePos[0] : null;
2349
- const position = location ? ` (line ${location.line + 1}, column ${location.col + 1})` : "";
2350
- throw new DocScribeParseError(`Invalid YAML frontmatter${position}: ${error.message}`, {
2351
- cause: error
2352
- });
2353
- }
2354
- let raw;
2355
- try {
2356
- raw = document.toJS({ mapAsMap: false });
2357
- } catch (error) {
2358
- throw new DocScribeParseError("Failed to parse YAML frontmatter", {
2359
- cause: error instanceof Error ? error : void 0
2360
- });
2361
- }
2362
- if (raw == null) {
2363
- return {};
2364
- }
2365
- if (typeof raw !== "object" || Array.isArray(raw)) {
2366
- throw new DocScribeParseError("YAML frontmatter must be a mapping at the top level");
2367
- }
2368
- return normalizeMetadata(raw);
2369
- }
2370
- function normalizeMetadata(raw) {
2371
- const normalized = {};
2372
- for (const [key, value] of Object.entries(raw)) {
2373
- normalized[key] = normalizeMetadataValue(value);
2374
- }
2375
- const base = {};
2376
- for (const [key, value] of Object.entries(normalized)) {
2377
- if (STRING_KEYS.has(key) || key === "tags" || key === "relatedDocs") {
2378
- continue;
2379
- }
2380
- base[key] = value;
2381
- }
2382
- const metadata = {
2383
- ...base,
2384
- ...normalized.title !== void 0 ? { title: ensureStringValue(normalized.title) } : {},
2385
- ...normalized.description !== void 0 ? { description: ensureStringValue(normalized.description) } : {},
2386
- ...normalized.author !== void 0 ? { author: ensureStringValue(normalized.author) } : {},
2387
- ...normalized.date !== void 0 ? { date: ensureStringValue(normalized.date) } : {},
2388
- ...normalized.lastUpdated !== void 0 ? { lastUpdated: ensureStringValue(normalized.lastUpdated) } : {},
2389
- ...normalized.status !== void 0 ? { status: ensureStringValue(normalized.status) } : {},
2390
- ...normalized.version !== void 0 ? { version: ensureStringValue(normalized.version) } : {},
2391
- ...normalized.tags !== void 0 ? (() => {
2392
- const tags = ensureStringArray(normalized.tags);
2393
- return tags.length > 0 ? { tags } : {};
2394
- })() : {},
2395
- ...normalized.relatedDocs !== void 0 ? (() => {
2396
- const related = ensureStringArray(normalized.relatedDocs);
2397
- return related.length > 0 ? { relatedDocs: related } : {};
2398
- })() : {}
2399
- };
2400
- return metadata;
2401
- }
2402
- function normalizeMetadataValue(value) {
2403
- if (value === null) {
2404
- return null;
2405
- }
2406
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2407
- return value;
2408
- }
2409
- if (value instanceof Date) {
2410
- return value.toISOString();
2411
- }
2412
- if (Array.isArray(value)) {
2413
- return value.map(
2414
- (item) => normalizeMetadataValue(item)
2415
- );
2416
- }
2417
- if (typeof value === "object") {
2418
- const result = {};
2419
- for (const [key, nested] of Object.entries(value)) {
2420
- result[key] = normalizeMetadataValue(nested);
2421
- }
2422
- return result;
2423
- }
2424
- return String(value);
2425
- }
2426
- function ensureStringValue(value) {
2427
- if (value === null) {
2428
- return "";
2429
- }
2430
- if (typeof value === "string") {
2431
- return value;
2432
- }
2433
- if (typeof value === "number" || typeof value === "boolean") {
2434
- return String(value);
2435
- }
2436
- if (Array.isArray(value)) {
2437
- return value.flatMap((item) => ensureStringArrayItem(item)).join(", ");
2438
- }
2439
- return JSON.stringify(value);
2440
- }
2441
- function ensureStringArray(value) {
2442
- return ensureStringArrayItem(value);
2443
- }
2444
- function ensureStringArrayItem(value) {
2445
- if (value === null) {
2446
- return [];
2447
- }
2448
- if (typeof value === "string") {
2449
- return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
2450
- }
2451
- if (typeof value === "number" || typeof value === "boolean") {
2452
- return [String(value)];
2453
- }
2454
- if (Array.isArray(value)) {
2455
- return value.flatMap((item) => ensureStringArrayItem(item));
2456
- }
2457
- return [JSON.stringify(value)];
2458
- }
2459
-
2460
- // src/docscribe/headers.ts
2461
- var ATX_PATTERN = /^ {0,3}(#{1,6})(?:[ \t]+|$)(.*)$/;
2462
- var SETEXT_PATTERN = /^ {0,3}(=+|-+)\s*$/;
2463
- function extractHeaders(input, options) {
2464
- const { content } = normalizeInput(input);
2465
- const parsed = parseFrontmatter(content);
2466
- const body = parsed.body;
2467
- const lineOffset = parsed.bodyStartLine;
2468
- const slugify = options?.slugify ?? defaultSlugify;
2469
- const maxDepth = options?.maxDepth ?? 6;
2470
- const lines = body.split("\n");
2471
- const headers = [];
2472
- let inFence = false;
2473
- let fenceMarker = null;
2474
- for (let index = 0; index < lines.length; index += 1) {
2475
- const line = lines[index];
2476
- const trimmed = line.trimEnd();
2477
- const lineNumber = lineOffset + index;
2478
- const fenceMatch = line.match(/^ {0,3}(```+|~~~+)/);
2479
- if (fenceMatch) {
2480
- const marker = fenceMatch[1][0];
2481
- if (!inFence) {
2482
- inFence = true;
2483
- fenceMarker = marker;
2484
- } else if (fenceMarker === marker) {
2485
- inFence = false;
2486
- fenceMarker = null;
2487
- }
2488
- continue;
2489
- }
2490
- if (inFence) {
2491
- continue;
2492
- }
2493
- if (/^( {4}|\t)/.test(line)) {
2494
- continue;
2495
- }
2496
- const atxMatch = trimmed.match(ATX_PATTERN);
2497
- if (atxMatch) {
2498
- const level = atxMatch[1].length;
2499
- if (level <= maxDepth) {
2500
- const rawText = atxMatch[2].replace(/#+\s*$/, "").trim();
2501
- const text = rawText.length > 0 ? rawText : "";
2502
- const slug = ensureSlug(slugify, text, lineNumber);
2503
- headers.push({ level, text, slug, line: lineNumber });
2504
- }
2505
- continue;
2506
- }
2507
- if (trimmed.length === 0) {
2508
- continue;
2509
- }
2510
- if (index + 1 >= lines.length) {
2511
- continue;
2512
- }
2513
- const nextLine = lines[index + 1];
2514
- const setextMatch = SETEXT_PATTERN.exec(nextLine.trimEnd());
2515
- if (setextMatch) {
2516
- const underline = setextMatch[1];
2517
- const level = underline.startsWith("=") ? 1 : 2;
2518
- if (level <= maxDepth) {
2519
- const text = trimmed.trim();
2520
- if (text.length > 0 && !/^ {0,3}(```|~~~)/.test(text)) {
2521
- const slug = ensureSlug(slugify, text, lineNumber);
2522
- headers.push({ level, text, slug, line: lineNumber });
2523
- }
2524
- }
2525
- index += 1;
2526
- }
2527
- }
2528
- return headers;
2529
- }
2530
- function defaultSlugify(value) {
2531
- return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-");
2532
- }
2533
- function ensureSlug(slugify, text, lineNumber) {
2534
- const candidate = slugify(text);
2535
- if (candidate && candidate.trim().length > 0) {
2536
- return candidate;
2537
- }
2538
- return `section-${lineNumber}`;
2539
- }
2540
-
2541
- // src/docscribe/format.ts
2542
- var JSON_PATTERN = /^[\s\t]*[[{]/;
2543
- var TOML_ASSIGNMENT = /^[A-Za-z0-9_"'\-.]+\s*=\s*.+$/;
2544
- var TOML_TABLE = /^\s*\[[^\]]+\]\s*$/;
2545
- var YAML_KEY_PATTERN = /^\s*[\w"'-]+\s*:\s*.+$/;
2546
- var MARKDOWN_HEADING = /(\n|^)\s{0,3}#{1,6}\s+/;
2547
- var MARKDOWN_LIST = /(\n|^)\s{0,3}(?:-|\*|\+)\s+/;
2548
- var MARKDOWN_SETEXT = /(\n|^).+\n\s{0,3}(?:=+|-+)\s*(\n|$)/;
2549
- function detectFormat(input) {
2550
- const { content } = normalizeInput(input);
2551
- return detectFormatFromContent(content);
2552
- }
2553
- function inspectDocument(input, options) {
2554
- const { content } = normalizeInput(input);
2555
- const format = detectFormatFromContent(content);
2556
- const frontmatter = parseFrontmatter(content);
2557
- const headers = extractHeaders(content, options);
2558
- const headerCount = headers.length;
2559
- const estimatedSections = headerCount > 0 ? headerCount : frontmatter.body.trim().length > 0 ? 1 : 0;
2560
- const lineCount = content.length === 0 ? 0 : content.split("\n").length;
2561
- const size = content.length;
2562
- return {
2563
- format,
2564
- hasFrontmatter: frontmatter.metadata !== null,
2565
- frontmatterRange: frontmatter.range,
2566
- metadata: frontmatter.metadata,
2567
- headerCount,
2568
- estimatedSections,
2569
- bodyStartLine: frontmatter.bodyStartLine,
2570
- lineCount,
2571
- size,
2572
- headers
2573
- };
2574
- }
2575
- function detectFormatFromContent(content) {
2576
- const trimmed = content.trim();
2577
- if (trimmed.length === 0) {
2578
- return "plain";
2579
- }
2580
- if (looksLikeJson(trimmed)) {
2581
- return "json";
2582
- }
2583
- if (looksLikeToml(trimmed)) {
2584
- return "toml";
2585
- }
2586
- const yamlSeparatorCount = countYamlSeparators(content);
2587
- if (yamlSeparatorCount >= 1 && isLikelyYamlStream(content)) {
2588
- return "yaml-stream";
2589
- }
2590
- const markdownIndicators = hasMarkdownIndicators(content);
2591
- if (looksLikeYaml(trimmed) && !markdownIndicators) {
2592
- return "yaml";
2593
- }
2594
- const frontmatter = parseFrontmatter(content);
2595
- if (frontmatter.metadata !== null) {
2596
- const body = frontmatter.body.trim();
2597
- if (markdownIndicators || MARKDOWN_HEADING.test(body) || MARKDOWN_LIST.test(body)) {
2598
- return "markdown";
2599
- }
2600
- if (!body) {
2601
- return "markdown";
2602
- }
2603
- }
2604
- if (markdownIndicators) {
2605
- return "markdown";
2606
- }
2607
- return "plain";
2608
- }
2609
- function looksLikeJson(trimmed) {
2610
- if (!JSON_PATTERN.test(trimmed)) {
2611
- return false;
2612
- }
2613
- try {
2614
- JSON.parse(trimmed);
2615
- return true;
2616
- } catch (_error) {
2617
- return false;
2618
- }
2619
- }
2620
- function looksLikeToml(content) {
2621
- const lines = content.split("\n");
2622
- let tableCount = 0;
2623
- let assignmentCount = 0;
2624
- for (const rawLine of lines) {
2625
- const line = rawLine.trim();
2626
- if (line === "" || line.startsWith("#")) {
2627
- continue;
2628
- }
2629
- if (TOML_TABLE.test(line)) {
2630
- tableCount += 1;
2631
- continue;
2632
- }
2633
- if (TOML_ASSIGNMENT.test(line)) {
2634
- assignmentCount += 1;
2635
- }
2636
- }
2637
- return tableCount > 0 || assignmentCount >= 3;
2638
- }
2639
- function looksLikeYaml(trimmed) {
2640
- if (trimmed.startsWith("%YAML")) {
2641
- return true;
2642
- }
2643
- const lines = trimmed.split("\n");
2644
- let signal = 0;
2645
- for (const raw of lines.slice(0, 20)) {
2646
- const line = raw.trim();
2647
- if (line === "" || line.startsWith("#")) {
2648
- continue;
2649
- }
2650
- if (line === "---") {
2651
- signal += 1;
2652
- continue;
2653
- }
2654
- if (YAML_KEY_PATTERN.test(line) || /^-\s/.test(line)) {
2655
- signal += 1;
2656
- }
2657
- }
2658
- return signal >= 2;
2659
- }
2660
- function countYamlSeparators(content) {
2661
- const matches = content.match(/(^|\n)---\s*(\n|$)/g);
2662
- return matches ? matches.length : 0;
2663
- }
2664
- function isLikelyYamlStream(content) {
2665
- const lines = content.split("\n");
2666
- let yamlSignals = 0;
2667
- let markdownSignals = 0;
2668
- for (const rawLine of lines) {
2669
- const line = rawLine.trim();
2670
- if (line === "" || line === "---") {
2671
- continue;
2672
- }
2673
- if (/^#/.test(line) || /^\s{0,3}(?:-|\*|\+)\s+/.test(line) || /(\[[^\]]+\]\([^)]+\))/.test(line)) {
2674
- markdownSignals += 1;
2675
- }
2676
- if (/:\s/.test(line) || /^-\s/.test(line)) {
2677
- yamlSignals += 1;
2678
- }
2679
- }
2680
- return yamlSignals >= 2 && markdownSignals === 0;
2681
- }
2682
- function hasMarkdownIndicators(content) {
2683
- return MARKDOWN_HEADING.test(content) || MARKDOWN_LIST.test(content) || MARKDOWN_SETEXT.test(content) || /(\n|^)\s{0,3}>\s+/.test(content) || /(\[[^\]]+\]\([^)]+\))/.test(content);
2684
- }
2685
- var __internal = {
2686
- detectFormatFromContent,
2687
- looksLikeJson,
2688
- looksLikeToml,
2689
- looksLikeYaml,
2690
- isLikelyYamlStream
2691
- };
2692
-
2693
- // src/docscribe/split.ts
2694
- function splitDocuments(input, options) {
2695
- const { content } = normalizeInput(input);
2696
- const trimmed = content.trim();
2697
- if (trimmed.length === 0) {
2698
- return [];
2699
- }
2700
- const maxDocuments = options?.maxDocuments ?? Number.POSITIVE_INFINITY;
2701
- const format = detectFormat(content);
2702
- let splits;
2703
- if (format === "yaml-stream") {
2704
- splits = withIndices(splitYamlStream(content));
2705
- } else if (format === "markdown") {
2706
- const markdownSplits = splitMarkdownDocuments(content);
2707
- splits = markdownSplits.length > 0 ? withIndices(markdownSplits) : [withIndex(buildSingleSplit(content, format, 1), 0)];
2708
- } else {
2709
- splits = [withIndex(buildSingleSplit(content, format, 1), 0)];
2710
- }
2711
- return applyDocumentLimit(splits, maxDocuments);
2712
- }
2713
- function splitYamlStream(content) {
2714
- const lines = content.split("\n");
2715
- const docs = [];
2716
- let current = { lines: [], startLine: 1 };
2717
- let delimiterCount = 0;
2718
- const flush = () => {
2719
- if (current.lines.join("\n").trim().length === 0) {
2720
- current = { lines: [], startLine: current.startLine };
2721
- return;
2722
- }
2723
- docs.push({ ...current, lines: [...current.lines] });
2724
- current = { lines: [], startLine: current.startLine };
2725
- };
2726
- for (let index = 0; index < lines.length; index += 1) {
2727
- const lineNumber = index + 1;
2728
- const line = lines[index];
2729
- const trimmed = line.trim();
2730
- if (trimmed === "---") {
2731
- delimiterCount += 1;
2732
- flush();
2733
- current = { lines: [], startLine: lineNumber + 1 };
2734
- continue;
2735
- }
2736
- if (delimiterCount === 0) {
2737
- current.startLine = 1;
2738
- }
2739
- current.lines.push(line);
2740
- }
2741
- flush();
2742
- if (docs.length === 0) {
2743
- return [buildRawSplit(content, "yaml", null, 1, content.split("\n").length)];
2744
- }
2745
- return docs.map((doc) => {
2746
- const joined = doc.lines.join("\n");
2747
- const lineCount = doc.lines.length;
2748
- return buildRawSplit(
2749
- joined,
2750
- "yaml",
2751
- null,
2752
- doc.startLine,
2753
- doc.startLine + Math.max(lineCount - 1, 0)
2754
- );
2755
- }).filter((split) => split.content.trim().length > 0);
2756
- }
2757
- function splitMarkdownDocuments(content) {
2758
- const frontmatter = parseFrontmatter(content);
2759
- const lines = content.split("\n");
2760
- const docs = [];
2761
- const frontmatterLimit = frontmatter.metadata ? Math.max(frontmatter.bodyStartLine - 1, 0) : 0;
2762
- let current = { lines: [], startLine: 1 };
2763
- let inFence = false;
2764
- let fenceMarker = null;
2765
- const flush = () => {
2766
- if (current.lines.join("\n").trim().length === 0) {
2767
- current = { lines: [], startLine: current.startLine };
2768
- return;
2769
- }
2770
- docs.push({ ...current, lines: [...current.lines] });
2771
- current = { lines: [], startLine: current.startLine };
2772
- };
2773
- for (let index = 0; index < lines.length; index += 1) {
2774
- const lineNumber = index + 1;
2775
- const line = lines[index];
2776
- const trimmed = line.trimEnd();
2777
- const fenceMatch = line.match(/^ {0,3}(```+|~~~+)/);
2778
- if (fenceMatch) {
2779
- const marker = fenceMatch[1][0];
2780
- if (!inFence) {
2781
- inFence = true;
2782
- fenceMarker = marker;
2783
- } else if (fenceMarker === marker) {
2784
- inFence = false;
2785
- fenceMarker = null;
2786
- }
2787
- }
2788
- const isPrimaryFrontmatter = frontmatterLimit > 0 && lineNumber <= frontmatterLimit;
2789
- if (!inFence && !isPrimaryFrontmatter && trimmed === "---") {
2790
- const prev = findPrevNonEmpty(lines, index - 1);
2791
- const next = findNextNonEmpty(lines, index + 1);
2792
- const prevLooksLikeMetadata = prev ? /:\s*/.test(prev.line) : false;
2793
- const nextIsHeading = next ? next.line.trim().startsWith("#") : false;
2794
- if (next && !prevLooksLikeMetadata && nextIsHeading) {
2795
- flush();
2796
- current = { lines: [], startLine: next.lineNumber };
2797
- continue;
2798
- }
2799
- }
2800
- current.lines.push(line);
2801
- }
2802
- flush();
2803
- return docs.map((doc) => {
2804
- const contentValue = doc.lines.join("\n");
2805
- const parsed = parseFrontmatter(contentValue);
2806
- const lineCount = doc.lines.length;
2807
- return buildRawSplit(
2808
- contentValue,
2809
- "markdown",
2810
- parsed.metadata,
2811
- doc.startLine,
2812
- doc.startLine + Math.max(lineCount - 1, 0),
2813
- parsed.metadata !== null
2814
- );
2815
- }).filter((split) => split.content.trim().length > 0);
2816
- }
2817
- function buildSingleSplit(content, format, startLine) {
2818
- const parsed = parseFrontmatter(content);
2819
- const lineCount = content.length === 0 ? 0 : content.split("\n").length;
2820
- return buildRawSplit(
2821
- content,
2822
- format,
2823
- parsed.metadata,
2824
- startLine,
2825
- startLine + Math.max(lineCount - 1, 0),
2826
- parsed.metadata !== null
2827
- );
2828
- }
2829
- function buildRawSplit(content, format, metadata, startLine, endLine, hasFrontmatter = false) {
2830
- return {
2831
- content,
2832
- format,
2833
- hasFrontmatter,
2834
- metadata: metadata ?? null,
2835
- startLine,
2836
- endLine
2837
- };
2838
- }
2839
- function applyDocumentLimit(splits, maxDocuments) {
2840
- if (maxDocuments <= 0) {
2841
- return [];
2842
- }
2843
- if (splits.length <= maxDocuments) {
2844
- return splits.map((split, idx) => ({ ...split, index: idx }));
2845
- }
2846
- const cutoff = Math.max(maxDocuments - 1, 0);
2847
- const allowed = splits.slice(0, cutoff);
2848
- const remaining = splits.slice(cutoff);
2849
- const merged = mergeSplitGroup(remaining);
2850
- const combined = cutoff === 0 ? [merged] : [...allowed, merged];
2851
- return combined.map((split, idx) => ({ ...split, index: idx }));
2852
- }
2853
- function mergeSplitGroup(splits) {
2854
- const first = splits[0];
2855
- const last = splits[splits.length - 1];
2856
- const combinedContent = splits.map((split) => split.content).join("\n---\n");
2857
- return {
2858
- content: combinedContent,
2859
- format: first.format,
2860
- hasFrontmatter: first.hasFrontmatter,
2861
- metadata: first.metadata,
2862
- startLine: first.startLine,
2863
- endLine: last.endLine,
2864
- index: 0
2865
- };
2866
- }
2867
- function withIndices(rawSplits) {
2868
- return rawSplits.map((split, idx) => withIndex(split, idx));
2869
- }
2870
- function withIndex(split, index) {
2871
- return { ...split, index };
2872
- }
2873
- function findNextNonEmpty(lines, startIndex) {
2874
- for (let index = startIndex; index < lines.length; index += 1) {
2875
- const candidate = lines[index];
2876
- if (candidate.trim().length === 0) {
2877
- continue;
2878
- }
2879
- return { line: candidate, lineNumber: index + 1 };
2880
- }
2881
- return null;
2882
- }
2883
- function findPrevNonEmpty(lines, startIndex) {
2884
- for (let index = startIndex; index >= 0; index -= 1) {
2885
- const candidate = lines[index];
2886
- if (candidate.trim().length === 0) {
2887
- continue;
2888
- }
2889
- return { line: candidate, lineNumber: index + 1 };
2890
- }
2891
- return null;
2892
- }
2893
-
2894
- // src/crucible/fulpack/types.ts
2895
- var ArchiveFormat = /* @__PURE__ */ ((ArchiveFormat2) => {
2896
- ArchiveFormat2["TAR"] = "tar";
2897
- ArchiveFormat2["TAR_GZ"] = "tar.gz";
2898
- ArchiveFormat2["ZIP"] = "zip";
2899
- ArchiveFormat2["GZIP"] = "gzip";
2900
- return ArchiveFormat2;
2901
- })(ArchiveFormat || {});
2902
- var EntryType = /* @__PURE__ */ ((EntryType2) => {
2903
- EntryType2["FILE"] = "file";
2904
- EntryType2["DIRECTORY"] = "directory";
2905
- EntryType2["SYMLINK"] = "symlink";
2906
- return EntryType2;
2907
- })(EntryType || {});
2908
- var Operation = /* @__PURE__ */ ((Operation2) => {
2909
- Operation2["CREATE"] = "create";
2910
- Operation2["EXTRACT"] = "extract";
2911
- Operation2["SCAN"] = "scan";
2912
- Operation2["VERIFY"] = "verify";
2913
- Operation2["INFO"] = "info";
2914
- return Operation2;
2915
- })(Operation || {});
2916
-
2917
- // src/fulpack/errors.ts
2918
- function createFulpackError(code, message, operation, context) {
2919
- return {
2920
- code,
2921
- message,
2922
- operation,
2923
- ...context?.path && { path: context.path },
2924
- ...context?.archive && { archive: context.archive },
2925
- ...context?.details && { details: context.details }
2926
- };
2927
- }
2928
- var ERROR_CODES = {
2929
- // Validation Errors
2930
- INVALID_ARCHIVE_FORMAT: "INVALID_ARCHIVE_FORMAT",
2931
- INVALID_PATH: "INVALID_PATH",
2932
- INVALID_OPTIONS: "INVALID_OPTIONS",
2933
- // Security Errors
2934
- PATH_TRAVERSAL: "PATH_TRAVERSAL",
2935
- ABSOLUTE_PATH: "ABSOLUTE_PATH",
2936
- SYMLINK_ESCAPE: "SYMLINK_ESCAPE",
2937
- DECOMPRESSION_BOMB: "DECOMPRESSION_BOMB",
2938
- CHECKSUM_MISMATCH: "CHECKSUM_MISMATCH",
2939
- // Runtime Errors
2940
- ARCHIVE_NOT_FOUND: "ARCHIVE_NOT_FOUND",
2941
- ARCHIVE_CORRUPT: "ARCHIVE_CORRUPT",
2942
- EXTRACTION_FAILED: "EXTRACTION_FAILED",
2943
- PERMISSION_DENIED: "PERMISSION_DENIED",
2944
- DISK_FULL: "DISK_FULL",
2945
- SOURCE_NOT_FOUND: "SOURCE_NOT_FOUND"
2946
- };
2947
- var FulpackOperationError = class extends Error {
2948
- code;
2949
- operation;
2950
- path;
2951
- archive;
2952
- details;
2953
- constructor(error) {
2954
- super(error.message);
2955
- this.name = "FulpackOperationError";
2956
- this.code = error.code;
2957
- this.operation = error.operation;
2958
- this.path = error.path;
2959
- this.archive = error.archive;
2960
- this.details = error.details;
2961
- }
2962
- /**
2963
- * Convert back to canonical error format
2964
- */
2965
- toCanonical() {
2966
- return {
2967
- code: this.code,
2968
- message: this.message,
2969
- operation: this.operation,
2970
- ...this.path && { path: this.path },
2971
- ...this.archive && { archive: this.archive },
2972
- ...this.details && { details: this.details }
2973
- };
2974
- }
2975
- };
2976
- function hasPathTraversal(path) {
2977
- return path.includes("..") || path.includes("\\..");
2978
- }
2979
- function isAbsolutePath(path) {
2980
- return path.startsWith("/") || /^[a-zA-Z]:/.test(path);
2981
- }
2982
- function validatePath(path, operation, allowAbsolute = false) {
2983
- if (!allowAbsolute && isAbsolutePath(path)) {
2984
- return createFulpackError(
2985
- ERROR_CODES.ABSOLUTE_PATH,
2986
- `Absolute path not allowed: ${path}`,
2987
- operation,
2988
- { path }
2989
- );
2990
- }
2991
- if (hasPathTraversal(path)) {
2992
- return createFulpackError(
2993
- ERROR_CODES.PATH_TRAVERSAL,
2994
- `Path traversal detected: ${path}`,
2995
- operation,
2996
- { path }
2997
- );
2998
- }
2999
- return null;
3000
- }
3001
- function checkDecompressionBomb(uncompressedSize, compressedSize, maxSize, maxEntries, currentEntries) {
3002
- if (uncompressedSize > maxSize) {
3003
- return createFulpackError(
3004
- ERROR_CODES.DECOMPRESSION_BOMB,
3005
- `Archive exceeds maximum size limit`,
3006
- "extract" /* EXTRACT */,
3007
- {
3008
- details: {
3009
- actual_size: uncompressedSize,
3010
- max_size: maxSize
3011
- }
3012
- }
3013
- );
3014
- }
3015
- if (currentEntries > maxEntries) {
3016
- return createFulpackError(
3017
- ERROR_CODES.DECOMPRESSION_BOMB,
3018
- `Archive exceeds maximum entry limit`,
3019
- "extract" /* EXTRACT */,
3020
- {
3021
- details: {
3022
- actual_size: currentEntries,
3023
- max_size: maxEntries
3024
- }
3025
- }
3026
- );
3027
- }
3028
- if (compressedSize > 0 && uncompressedSize / compressedSize > 100) {
3029
- return createFulpackError(
3030
- ERROR_CODES.DECOMPRESSION_BOMB,
3031
- `Suspicious compression ratio detected`,
3032
- "extract" /* EXTRACT */,
3033
- {
3034
- details: {
3035
- compression_ratio: uncompressedSize / compressedSize
3036
- }
3037
- }
3038
- );
3039
- }
3040
- return null;
3041
- }
3042
-
3043
- // src/fulpack/core.ts
3044
- var DEFAULTS = {
3045
- CREATE: {
3046
- compression_level: 6,
3047
- checksum_algorithm: "sha256",
3048
- preserve_permissions: true,
3049
- follow_symlinks: false
3050
- },
3051
- EXTRACT: {
3052
- overwrite: "error",
3053
- verify_checksums: true,
3054
- preserve_permissions: true,
3055
- max_size: 1024 * 1024 * 1024,
3056
- // 1GB
3057
- max_entries: 1e5
3058
- },
3059
- SCAN: {
3060
- include_metadata: true,
3061
- max_entries: 1e5
3062
- }
3063
- };
3064
- async function create(source, output, format, options) {
3065
- const opts = { ...DEFAULTS.CREATE, ...options };
3066
- const sources = Array.isArray(source) ? source : [source];
3067
- if (format !== "tar" /* TAR */ && format !== "tar.gz" /* TAR_GZ */ && format !== "zip" /* ZIP */ && format !== "gzip" /* GZIP */) {
3068
- throw new FulpackOperationError(
3069
- createFulpackError(
3070
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
3071
- `Format ${format} not yet supported`,
3072
- "create" /* CREATE */
3073
- )
3074
- );
3075
- }
3076
- const outputDir = dirname(output);
3077
- if (!existsSync(outputDir)) {
3078
- mkdirSync(outputDir, { recursive: true });
3079
- }
3080
- if (format === "zip" /* ZIP */) {
3081
- return await createZipArchive(sources, output, opts);
3082
- } else if (format === "tar.gz" /* TAR_GZ */) {
3083
- return await createTarGzArchive(sources, output, opts);
3084
- } else if (format === "tar" /* TAR */) {
3085
- return await createTarArchive(sources, output, opts);
3086
- } else if (format === "gzip" /* GZIP */) {
3087
- return await createGzipFile(sources, output, opts);
3088
- } else {
3089
- throw new FulpackOperationError(
3090
- createFulpackError(
3091
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
3092
- `Unsupported format for creation: ${format}`,
3093
- "create" /* CREATE */
3094
- )
3095
- );
3096
- }
3097
- }
3098
- async function extract(archive, destination, options) {
3099
- const opts = { ...DEFAULTS.EXTRACT, ...options };
3100
- if (!existsSync(archive)) {
3101
- throw new FulpackOperationError(
3102
- createFulpackError(
3103
- ERROR_CODES.ARCHIVE_NOT_FOUND,
3104
- `Archive not found: ${archive}`,
3105
- "extract" /* EXTRACT */,
3106
- { archive }
3107
- )
3108
- );
3109
- }
3110
- if (!existsSync(destination)) {
3111
- mkdirSync(destination, { recursive: true });
3112
- }
3113
- if (archive.endsWith(".tar.gz") || archive.endsWith(".tgz")) {
3114
- return await extractTarGz(archive, destination, opts);
3115
- } else if (archive.endsWith(".tar")) {
3116
- return await extractTar(archive, destination, opts);
3117
- } else if (archive.endsWith(".zip")) {
3118
- return await extractZip(archive, destination, opts);
3119
- } else if (archive.endsWith(".gz")) {
3120
- return await extractGzipFile(archive, destination, opts);
3121
- } else {
3122
- throw new FulpackOperationError(
3123
- createFulpackError(
3124
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
3125
- `Unsupported archive format for extraction: ${archive}`,
3126
- "extract" /* EXTRACT */,
3127
- { archive }
3128
- )
3129
- );
3130
- }
3131
- }
3132
- async function scan(archive, options) {
3133
- const opts = { ...DEFAULTS.SCAN, ...options };
3134
- if (!existsSync(archive)) {
3135
- throw new FulpackOperationError(
3136
- createFulpackError(
3137
- ERROR_CODES.ARCHIVE_NOT_FOUND,
3138
- `Archive not found: ${archive}`,
3139
- "scan" /* SCAN */,
3140
- { archive }
3141
- )
3142
- );
3143
- }
3144
- const filename = basename(archive);
3145
- if (filename.endsWith(".tar.gz") || filename.endsWith(".tgz")) {
3146
- return await scanTarGz(archive, opts);
3147
- } else if (filename.endsWith(".tar")) {
3148
- return await scanTar(archive, opts);
3149
- } else if (filename.endsWith(".zip")) {
3150
- return await scanZip(archive, opts);
3151
- } else if (filename.endsWith(".gz")) {
3152
- return await scanGzipFile(archive, opts);
3153
- } else {
3154
- throw new FulpackOperationError(
3155
- createFulpackError(
3156
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
3157
- `Unsupported archive format: ${filename}`,
3158
- "scan" /* SCAN */,
3159
- { archive }
3160
- )
3161
- );
3162
- }
3163
- }
3164
- async function verify(archive, _options) {
3165
- if (!existsSync(archive)) {
3166
- throw new FulpackOperationError(
3167
- createFulpackError(
3168
- ERROR_CODES.ARCHIVE_NOT_FOUND,
3169
- `Archive not found: ${archive}`,
3170
- "verify" /* VERIFY */,
3171
- { archive }
3172
- )
3173
- );
3174
- }
3175
- const stats = statSync(archive);
3176
- const errors = [];
3177
- const warnings = [];
3178
- const checks_performed = [];
3179
- let entry_count = 0;
3180
- let checksums_verified = 0;
3181
- checks_performed.push("structure_valid");
3182
- if (stats.size === 0) {
3183
- errors.push(
3184
- createFulpackError(ERROR_CODES.ARCHIVE_CORRUPT, "Archive file is empty", "verify" /* VERIFY */, {
3185
- archive
3186
- })
3187
- );
3188
- return {
3189
- valid: false,
3190
- errors,
3191
- warnings,
3192
- entry_count: 0,
3193
- checksums_verified: 0,
3194
- checks_performed
3195
- };
3196
- }
3197
- let entries;
3198
- try {
3199
- entries = await scan(archive, { include_metadata: true });
3200
- entry_count = entries.length;
3201
- } catch (error) {
3202
- errors.push(
3203
- createFulpackError(
3204
- ERROR_CODES.ARCHIVE_CORRUPT,
3205
- `Failed to scan archive: ${error instanceof Error ? error.message : String(error)}`,
3206
- "verify" /* VERIFY */,
3207
- { archive, details: { original_error: error } }
3208
- )
3209
- );
3210
- return {
3211
- valid: false,
3212
- errors,
3213
- warnings,
3214
- entry_count: 0,
3215
- checksums_verified: 0,
3216
- checks_performed
3217
- };
3218
- }
3219
- checks_performed.push("no_path_traversal");
3220
- for (const entry of entries) {
3221
- const pathError = validatePath(entry.path, "verify" /* VERIFY */, true);
3222
- if (pathError) {
3223
- errors.push({ ...pathError, archive });
3224
- }
3225
- }
3226
- const symlinks = entries.filter((e) => e.type === "symlink");
3227
- if (symlinks.length > 0) {
3228
- checks_performed.push("symlinks_safe");
3229
- for (const symlink of symlinks) {
3230
- if (symlink.symlink_target) {
3231
- const targetError = validatePath(symlink.symlink_target, "verify" /* VERIFY */, true);
3232
- if (targetError) {
3233
- errors.push(
3234
- createFulpackError(
3235
- ERROR_CODES.ARCHIVE_CORRUPT,
3236
- `Symlink '${symlink.path}' targets unsafe location: ${symlink.symlink_target}`,
3237
- "verify" /* VERIFY */,
3238
- { archive, path: symlink.path, details: { symlink_target: symlink.symlink_target } }
3239
- )
3240
- );
3241
- }
3242
- }
3243
- }
3244
- }
3245
- checks_performed.push("no_decompression_bomb");
3246
- const total_size = entries.reduce((sum, entry) => sum + (entry.size || 0), 0);
3247
- const compressed_size = stats.size;
3248
- const compression_ratio = total_size > 0 && compressed_size > 0 ? total_size / compressed_size : 1;
3249
- if (compression_ratio > 100) {
3250
- warnings.push(
3251
- `High compression ratio detected: ${compression_ratio.toFixed(1)}:1 (threshold: 100:1)`
3252
- );
3253
- }
3254
- const MAX_SIZE = 1024 * 1024 * 1024;
3255
- if (total_size > MAX_SIZE) {
3256
- warnings.push(
3257
- `Total uncompressed size (${(total_size / (1024 * 1024)).toFixed(1)}MB) exceeds recommended limit (1GB)`
3258
- );
3259
- }
3260
- const MAX_ENTRIES = 1e5;
3261
- if (entry_count > MAX_ENTRIES) {
3262
- warnings.push(`Entry count (${entry_count}) exceeds recommended limit (${MAX_ENTRIES})`);
3263
- }
3264
- const entriesWithChecksums = entries.filter((e) => e.checksum !== void 0);
3265
- if (entriesWithChecksums.length > 0) {
3266
- checks_performed.push("checksums_verified");
3267
- checksums_verified = entriesWithChecksums.length;
3268
- } else if (entry_count > 0) {
3269
- warnings.push("Archive does not contain checksums for integrity verification");
3270
- }
3271
- return {
3272
- valid: errors.length === 0,
3273
- errors,
3274
- warnings,
3275
- entry_count,
3276
- checksums_verified,
3277
- checks_performed
3278
- };
3279
- }
3280
- async function info(archive) {
3281
- if (!existsSync(archive)) {
3282
- throw new FulpackOperationError(
3283
- createFulpackError(
3284
- ERROR_CODES.ARCHIVE_NOT_FOUND,
3285
- `Archive not found: ${archive}`,
3286
- "info" /* INFO */,
3287
- { archive }
3288
- )
3289
- );
3290
- }
3291
- const stats = statSync(archive);
3292
- const filename = basename(archive);
3293
- let format;
3294
- let compression = "none";
3295
- if (filename.endsWith(".tar.gz") || filename.endsWith(".tgz")) {
3296
- format = "tar.gz" /* TAR_GZ */;
3297
- compression = "gzip";
3298
- } else if (filename.endsWith(".zip")) {
3299
- format = "zip" /* ZIP */;
3300
- compression = "deflate";
3301
- } else if (filename.endsWith(".gz")) {
3302
- format = "gzip" /* GZIP */;
3303
- compression = "gzip";
3304
- } else if (filename.endsWith(".tar")) {
3305
- format = "tar" /* TAR */;
3306
- compression = "none";
3307
- } else {
3308
- throw new FulpackOperationError(
3309
- createFulpackError(
3310
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
3311
- `Unsupported archive format: ${filename}`,
3312
- "info" /* INFO */,
3313
- { archive }
3314
- )
3315
- );
3316
- }
3317
- const entries = await scan(archive, { include_metadata: true });
3318
- const entry_count = entries.length;
3319
- const total_size = entries.reduce((sum, entry) => sum + (entry.size || 0), 0);
3320
- const compressed_size = stats.size;
3321
- const compression_ratio = total_size > 0 ? compressed_size / total_size : 1;
3322
- const has_checksums = entries.some((entry) => entry.checksum !== void 0);
3323
- return {
3324
- format,
3325
- compression,
3326
- entry_count,
3327
- total_size,
3328
- compressed_size,
3329
- compression_ratio,
3330
- has_checksums,
3331
- created: stats.mtime.toISOString()
3332
- };
3333
- }
3334
- async function extractGzipFile(archive, destination, options) {
3335
- const filename = basename(archive, ".gz");
3336
- const outputPath = join(destination, filename);
3337
- if (existsSync(outputPath)) {
3338
- if (options.overwrite === "error") {
3339
- throw new FulpackOperationError(
3340
- createFulpackError(
3341
- ERROR_CODES.EXTRACTION_FAILED,
3342
- `Output file already exists: ${outputPath}`,
3343
- "extract" /* EXTRACT */,
3344
- { path: outputPath, archive }
3345
- )
3346
- );
3347
- } else if (options.overwrite === "skip") {
3348
- return {
3349
- extracted_count: 0,
3350
- skipped_count: 1,
3351
- error_count: 0,
3352
- errors: [],
3353
- warnings: [`Skipped existing file: ${outputPath}`]
3354
- };
3355
- }
3356
- }
3357
- try {
3358
- const readStream = createReadStream(archive);
3359
- const writeStream = createWriteStream(outputPath);
3360
- const gunzip = createGunzip();
3361
- await new Promise((resolve, reject) => {
3362
- pipeline(readStream, gunzip, writeStream, (error) => {
3363
- if (error) {
3364
- reject(error);
3365
- } else {
3366
- resolve(void 0);
3367
- }
3368
- });
3369
- });
3370
- return {
3371
- extracted_count: 1,
3372
- skipped_count: 0,
3373
- error_count: 0,
3374
- errors: [],
3375
- warnings: []
3376
- };
3377
- } catch (error) {
3378
- throw new FulpackOperationError(
3379
- createFulpackError(
3380
- ERROR_CODES.EXTRACTION_FAILED,
3381
- `Failed to extract gzip file: ${error instanceof Error ? error.message : "Unknown error"}`,
3382
- "extract" /* EXTRACT */,
3383
- { archive, details: { original_error: error } }
3384
- )
3385
- );
3386
- }
3387
- }
3388
- async function scanGzipFile(archive, options) {
3389
- const filename = basename(archive, ".gz");
3390
- const stats = statSync(archive);
3391
- const entry = {
3392
- path: filename,
3393
- type: "file" /* FILE */,
3394
- size: 0,
3395
- // Would need to decompress to get actual size
3396
- compressed_size: stats.size,
3397
- modified: stats.mtime.toISOString(),
3398
- ...options.include_metadata && { mode: "0644" }
3399
- // Default for gzip files
3400
- };
3401
- return [entry];
3402
- }
3403
- async function createTarGzArchive(sources, output, options) {
3404
- const writeStream = createWriteStream(output);
3405
- const archive = new TarArchive({
3406
- gzip: true,
3407
- gzipOptions: {
3408
- level: options.compression_level
3409
- }
3410
- });
3411
- let entryCount = 0;
3412
- let totalSize = 0;
3413
- const writePromise = new Promise((resolve, reject) => {
3414
- writeStream.on("close", () => resolve());
3415
- writeStream.on("error", reject);
3416
- archive.on(
3417
- "error",
3418
- (error) => reject(
3419
- new FulpackOperationError(
3420
- createFulpackError(
3421
- ERROR_CODES.EXTRACTION_FAILED,
3422
- `TAR.GZ creation failed: ${error.message}`,
3423
- "create" /* CREATE */,
3424
- { details: { original_error: error } }
3425
- )
3426
- )
3427
- )
3428
- );
3429
- });
3430
- archive.pipe(writeStream);
3431
- for (const sourcePath of sources) {
3432
- if (!existsSync(sourcePath)) {
3433
- throw new FulpackOperationError(
3434
- createFulpackError(
3435
- ERROR_CODES.SOURCE_NOT_FOUND,
3436
- `Source not found: ${sourcePath}`,
3437
- "create" /* CREATE */,
3438
- { details: { source: sourcePath } }
3439
- )
3440
- );
3441
- }
3442
- const stats = statSync(sourcePath);
3443
- if (stats.isDirectory()) {
3444
- await addDirectoryToTarGzArchive(archive, sourcePath, "", options);
3445
- } else {
3446
- const entryName = basename(sourcePath);
3447
- archive.file(sourcePath, {
3448
- name: entryName,
3449
- mode: options.preserve_permissions ? stats.mode : 420
3450
- });
3451
- entryCount++;
3452
- totalSize += stats.size;
3453
- }
3454
- }
3455
- archive.finalize().catch(() => {
3456
- });
3457
- await writePromise;
3458
- const outputStats = statSync(output);
3459
- return {
3460
- format: "tar.gz" /* TAR_GZ */,
3461
- compression: "gzip",
3462
- entry_count: entryCount,
3463
- total_size: totalSize,
3464
- compressed_size: outputStats.size,
3465
- compression_ratio: totalSize > 0 ? outputStats.size / totalSize : 0,
3466
- has_checksums: false,
3467
- // TODO: Add fulhash checksum integration
3468
- created: outputStats.mtime.toISOString()
3469
- };
3470
- }
3471
- async function createZipArchive(sources, output, options) {
3472
- const writeStream = createWriteStream(output);
3473
- const archive = new ZipArchive({
3474
- zlib: { level: options.compression_level }
3475
- });
3476
- let entryCount = 0;
3477
- let totalSize = 0;
3478
- const writePromise = new Promise((resolve, reject) => {
3479
- writeStream.on("close", () => resolve());
3480
- writeStream.on("error", reject);
3481
- archive.on(
3482
- "error",
3483
- (error) => reject(
3484
- new FulpackOperationError(
3485
- createFulpackError(
3486
- ERROR_CODES.EXTRACTION_FAILED,
3487
- `ZIP creation failed: ${error.message}`,
3488
- "create" /* CREATE */,
3489
- { details: { original_error: error } }
3490
- )
3491
- )
3492
- )
3493
- );
3494
- });
3495
- archive.pipe(writeStream);
3496
- for (const sourcePath of sources) {
3497
- if (!existsSync(sourcePath)) {
3498
- throw new FulpackOperationError(
3499
- createFulpackError(
3500
- ERROR_CODES.SOURCE_NOT_FOUND,
3501
- `Source not found: ${sourcePath}`,
3502
- "create" /* CREATE */,
3503
- { details: { source: sourcePath } }
3504
- )
3505
- );
3506
- }
3507
- const stats = statSync(sourcePath);
3508
- if (stats.isDirectory()) {
3509
- await addDirectoryToZipArchive(archive, sourcePath, "", options);
3510
- } else {
3511
- const entryName = basename(sourcePath);
3512
- archive.file(sourcePath, {
3513
- name: entryName,
3514
- mode: options.preserve_permissions ? stats.mode : 420
3515
- });
3516
- entryCount++;
3517
- totalSize += stats.size;
3518
- }
3519
- }
3520
- archive.finalize().catch(() => {
3521
- });
3522
- await writePromise;
3523
- const outputStats = statSync(output);
3524
- return {
3525
- format: "zip" /* ZIP */,
3526
- compression: "deflate",
3527
- entry_count: entryCount,
3528
- total_size: totalSize,
3529
- compressed_size: outputStats.size,
3530
- compression_ratio: totalSize > 0 ? outputStats.size / totalSize : 0,
3531
- has_checksums: false,
3532
- created: outputStats.mtime.toISOString()
3533
- };
3534
- }
3535
- async function addDirectoryToTarGzArchive(archive, dirPath, archivePrefix, options) {
3536
- const entries = readdirSync(dirPath);
3537
- for (const entry of entries) {
3538
- const fullPath = join(dirPath, entry);
3539
- const archivePath = join(archivePrefix, entry);
3540
- const stats = lstatSync(fullPath);
3541
- if (stats.isSymbolicLink()) {
3542
- if (!options.follow_symlinks) {
3543
- continue;
3544
- }
3545
- const targetStats = statSync(fullPath);
3546
- if (targetStats.isDirectory()) {
3547
- await addDirectoryToTarGzArchive(archive, fullPath, archivePath, options);
3548
- } else {
3549
- archive.file(fullPath, {
3550
- name: archivePath,
3551
- mode: options.preserve_permissions ? targetStats.mode : 420
3552
- });
3553
- }
3554
- } else if (stats.isDirectory()) {
3555
- await addDirectoryToTarGzArchive(archive, fullPath, archivePath, options);
3556
- } else {
3557
- archive.file(fullPath, {
3558
- name: archivePath,
3559
- mode: options.preserve_permissions ? stats.mode : 420
3560
- });
3561
- }
3562
- }
3563
- }
3564
- async function createTarArchive(sources, output, options) {
3565
- const writeStream = createWriteStream(output);
3566
- const archive = new TarArchive({
3567
- gzip: false
3568
- // Uncompressed
3569
- });
3570
- let entryCount = 0;
3571
- let totalSize = 0;
3572
- const writePromise = new Promise((resolve, reject) => {
3573
- writeStream.on("close", () => resolve());
3574
- writeStream.on("error", reject);
3575
- archive.on(
3576
- "error",
3577
- (error) => reject(
3578
- new FulpackOperationError(
3579
- createFulpackError(
3580
- ERROR_CODES.EXTRACTION_FAILED,
3581
- `TAR creation failed: ${error.message}`,
3582
- "create" /* CREATE */,
3583
- { details: { original_error: error } }
3584
- )
3585
- )
3586
- )
3587
- );
3588
- });
3589
- archive.pipe(writeStream);
3590
- for (const sourcePath of sources) {
3591
- if (!existsSync(sourcePath)) {
3592
- throw new FulpackOperationError(
3593
- createFulpackError(
3594
- ERROR_CODES.SOURCE_NOT_FOUND,
3595
- `Source not found: ${sourcePath}`,
3596
- "create" /* CREATE */,
3597
- { details: { source: sourcePath } }
3598
- )
3599
- );
3600
- }
3601
- const stats = statSync(sourcePath);
3602
- if (stats.isDirectory()) {
3603
- await addDirectoryToTarArchive(archive, sourcePath, "", options);
3604
- } else {
3605
- const entryName = basename(sourcePath);
3606
- archive.file(sourcePath, {
3607
- name: entryName,
3608
- mode: options.preserve_permissions ? stats.mode : 420
3609
- });
3610
- entryCount++;
3611
- totalSize += stats.size;
3612
- }
3613
- }
3614
- archive.finalize().catch(() => {
3615
- });
3616
- await writePromise;
3617
- const outputStats = statSync(output);
3618
- return {
3619
- format: "tar" /* TAR */,
3620
- compression: "none",
3621
- entry_count: entryCount,
3622
- total_size: totalSize,
3623
- compressed_size: outputStats.size,
3624
- compression_ratio: 1,
3625
- // Uncompressed
3626
- has_checksums: false,
3627
- // TODO: Add fulhash checksum integration
3628
- created: outputStats.mtime.toISOString()
3629
- };
3630
- }
3631
- async function addDirectoryToTarArchive(archive, dirPath, archivePrefix, options) {
3632
- const entries = readdirSync(dirPath);
3633
- for (const entry of entries) {
3634
- const fullPath = join(dirPath, entry);
3635
- const archivePath = join(archivePrefix, entry);
3636
- const stats = lstatSync(fullPath);
3637
- if (stats.isSymbolicLink()) {
3638
- if (!options.follow_symlinks) {
3639
- continue;
3640
- }
3641
- const targetStats = statSync(fullPath);
3642
- if (targetStats.isDirectory()) {
3643
- await addDirectoryToTarArchive(archive, fullPath, archivePath, options);
3644
- } else {
3645
- archive.file(fullPath, {
3646
- name: archivePath,
3647
- mode: options.preserve_permissions ? targetStats.mode : 420
3648
- });
3649
- }
3650
- } else if (stats.isDirectory()) {
3651
- await addDirectoryToTarArchive(archive, fullPath, archivePath, options);
3652
- } else {
3653
- archive.file(fullPath, {
3654
- name: archivePath,
3655
- mode: options.preserve_permissions ? stats.mode : 420
3656
- });
3657
- }
3658
- }
3659
- }
3660
- async function createGzipFile(sources, output, options) {
3661
- if (sources.length !== 1) {
3662
- throw new FulpackOperationError(
3663
- createFulpackError(
3664
- ERROR_CODES.INVALID_OPTIONS,
3665
- `GZIP format requires exactly one source file, got ${sources.length}`,
3666
- "create" /* CREATE */
3667
- )
3668
- );
3669
- }
3670
- const sourcePath = sources[0];
3671
- if (!existsSync(sourcePath)) {
3672
- throw new FulpackOperationError(
3673
- createFulpackError(
3674
- ERROR_CODES.SOURCE_NOT_FOUND,
3675
- `Source not found: ${sourcePath}`,
3676
- "create" /* CREATE */,
3677
- { details: { source: sourcePath } }
3678
- )
3679
- );
3680
- }
3681
- const stats = statSync(sourcePath);
3682
- if (stats.isDirectory()) {
3683
- throw new FulpackOperationError(
3684
- createFulpackError(
3685
- ERROR_CODES.INVALID_OPTIONS,
3686
- "GZIP format does not support directories",
3687
- "create" /* CREATE */,
3688
- { details: { source: sourcePath } }
3689
- )
3690
- );
3691
- }
3692
- const readStream = createReadStream(sourcePath);
3693
- const writeStream = createWriteStream(output);
3694
- const gzip = createGzip({ level: options.compression_level });
3695
- await new Promise((resolve, reject) => {
3696
- pipeline(readStream, gzip, writeStream, (error) => {
3697
- if (error) {
3698
- reject(error);
3699
- } else {
3700
- resolve(void 0);
3701
- }
3702
- });
3703
- });
3704
- const outputStats = statSync(output);
3705
- return {
3706
- format: "gzip" /* GZIP */,
3707
- compression: "gzip",
3708
- entry_count: 1,
3709
- total_size: stats.size,
3710
- compressed_size: outputStats.size,
3711
- compression_ratio: stats.size > 0 ? outputStats.size / stats.size : 0,
3712
- has_checksums: false,
3713
- created: outputStats.mtime.toISOString()
3714
- };
3715
- }
3716
- async function addDirectoryToZipArchive(archive, dirPath, archivePrefix, options) {
3717
- const entries = readdirSync(dirPath);
3718
- for (const entry of entries) {
3719
- const fullPath = join(dirPath, entry);
3720
- const archivePath = join(archivePrefix, entry);
3721
- const stats = lstatSync(fullPath);
3722
- if (stats.isSymbolicLink()) {
3723
- if (!options.follow_symlinks) {
3724
- continue;
3725
- }
3726
- const targetStats = statSync(fullPath);
3727
- if (targetStats.isDirectory()) {
3728
- await addDirectoryToZipArchive(archive, fullPath, archivePath, options);
3729
- } else {
3730
- archive.file(fullPath, {
3731
- name: archivePath,
3732
- mode: options.preserve_permissions ? targetStats.mode : 420
3733
- });
3734
- }
3735
- } else if (stats.isDirectory()) {
3736
- await addDirectoryToZipArchive(archive, fullPath, archivePath, options);
3737
- } else {
3738
- archive.file(fullPath, {
3739
- name: archivePath,
3740
- mode: options.preserve_permissions ? stats.mode : 420
3741
- });
3742
- }
3743
- }
3744
- }
3745
- async function extractTarGz(archive, destination, options) {
3746
- const tarStream = await import('tar-stream');
3747
- const extract2 = tarStream.extract();
3748
- let extractedCount = 0;
3749
- let skippedCount = 0;
3750
- let errorCount = 0;
3751
- const errors = [];
3752
- const warnings = [];
3753
- let totalExtractedSize = 0;
3754
- let entryCount = 0;
3755
- extract2.on("entry", async (header, stream, next) => {
3756
- entryCount++;
3757
- try {
3758
- if (options.max_entries && entryCount > options.max_entries) {
3759
- errors.push(
3760
- createFulpackError(
3761
- ERROR_CODES.DECOMPRESSION_BOMB,
3762
- `Entry count exceeds maximum (${options.max_entries})`,
3763
- "extract" /* EXTRACT */,
3764
- {
3765
- archive,
3766
- details: { entry_count: entryCount, max_entries: options.max_entries }
3767
- }
3768
- )
3769
- );
3770
- stream.resume();
3771
- next();
3772
- return;
3773
- }
3774
- const pathError = validatePath(header.name, "extract" /* EXTRACT */, false);
3775
- if (pathError) {
3776
- errors.push({ ...pathError, archive });
3777
- errorCount++;
3778
- stream.resume();
3779
- next();
3780
- return;
3781
- }
3782
- const outputPath = join(destination, header.name);
3783
- if (header.type === "directory") {
3784
- if (!existsSync(outputPath)) {
3785
- mkdirSync(outputPath, { recursive: true });
3786
- }
3787
- extractedCount++;
3788
- stream.resume();
3789
- next();
3790
- } else if (header.type === "file") {
3791
- if (existsSync(outputPath)) {
3792
- if (options.overwrite === "error") {
3793
- errors.push(
3794
- createFulpackError(
3795
- ERROR_CODES.EXTRACTION_FAILED,
3796
- `Output file already exists: ${outputPath}`,
3797
- "extract" /* EXTRACT */,
3798
- { path: header.name, archive }
3799
- )
3800
- );
3801
- errorCount++;
3802
- stream.resume();
3803
- next();
3804
- return;
3805
- } else if (options.overwrite === "skip") {
3806
- warnings.push(`Skipped existing file: ${outputPath}`);
3807
- skippedCount++;
3808
- stream.resume();
3809
- next();
3810
- return;
3811
- }
3812
- }
3813
- const parentDir = dirname(outputPath);
3814
- if (!existsSync(parentDir)) {
3815
- mkdirSync(parentDir, { recursive: true });
3816
- }
3817
- const writeStream = createWriteStream(outputPath);
3818
- stream.on("data", (chunk) => {
3819
- totalExtractedSize += chunk.length;
3820
- if (options.max_size && totalExtractedSize > options.max_size) {
3821
- stream.destroy();
3822
- writeStream.destroy();
3823
- errors.push(
3824
- createFulpackError(
3825
- ERROR_CODES.DECOMPRESSION_BOMB,
3826
- `Total extracted size exceeds maximum (${options.max_size} bytes)`,
3827
- "extract" /* EXTRACT */,
3828
- {
3829
- archive,
3830
- path: header.name,
3831
- details: { actual_size: totalExtractedSize, max_size: options.max_size }
3832
- }
3833
- )
3834
- );
3835
- errorCount++;
3836
- next();
3837
- return;
3838
- }
3839
- });
3840
- stream.pipe(writeStream);
3841
- writeStream.on("finish", () => {
3842
- if (options.preserve_permissions && header.mode) {
3843
- try {
3844
- const { chmodSync } = __require("fs");
3845
- chmodSync(outputPath, header.mode);
3846
- } catch {
3847
- }
3848
- }
3849
- extractedCount++;
3850
- next();
3851
- });
3852
- writeStream.on("error", (error) => {
3853
- errors.push(
3854
- createFulpackError(
3855
- ERROR_CODES.EXTRACTION_FAILED,
3856
- `Failed to write file: ${error.message}`,
3857
- "extract" /* EXTRACT */,
3858
- { path: header.name, archive, details: { original_error: error } }
3859
- )
3860
- );
3861
- errorCount++;
3862
- next();
3863
- });
3864
- } else {
3865
- warnings.push(`Skipped entry type ${header.type}: ${header.name}`);
3866
- stream.resume();
3867
- next();
3868
- }
3869
- } catch (error) {
3870
- errors.push(
3871
- createFulpackError(
3872
- ERROR_CODES.EXTRACTION_FAILED,
3873
- `Error processing entry: ${error instanceof Error ? error.message : "Unknown error"}`,
3874
- "extract" /* EXTRACT */,
3875
- { path: header.name, archive, details: { original_error: error } }
3876
- )
3877
- );
3878
- errorCount++;
3879
- stream.resume();
3880
- next();
3881
- }
3882
- });
3883
- const readStream = createReadStream(archive);
3884
- const gunzip = createGunzip();
3885
- return new Promise((resolve, reject) => {
3886
- extract2.on("finish", () => {
3887
- resolve({
3888
- extracted_count: extractedCount,
3889
- skipped_count: skippedCount,
3890
- error_count: errorCount,
3891
- errors: errors.length > 0 ? errors : void 0,
3892
- warnings: warnings.length > 0 ? warnings : void 0,
3893
- total_bytes: totalExtractedSize
3894
- });
3895
- });
3896
- extract2.on("error", (error) => {
3897
- reject(
3898
- new FulpackOperationError(
3899
- createFulpackError(
3900
- ERROR_CODES.EXTRACTION_FAILED,
3901
- `TAR.GZ extraction failed: ${error.message}`,
3902
- "extract" /* EXTRACT */,
3903
- { archive, details: { original_error: error } }
3904
- )
3905
- )
3906
- );
3907
- });
3908
- readStream.pipe(gunzip).pipe(extract2);
3909
- });
3910
- }
3911
- async function extractTar(archive, destination, options) {
3912
- const tarStream = await import('tar-stream');
3913
- const extract2 = tarStream.extract();
3914
- let extractedCount = 0;
3915
- let skippedCount = 0;
3916
- let errorCount = 0;
3917
- const errors = [];
3918
- const warnings = [];
3919
- let totalExtractedSize = 0;
3920
- let entryCount = 0;
3921
- extract2.on("entry", async (header, stream, next) => {
3922
- entryCount++;
3923
- try {
3924
- if (options.max_entries && entryCount > options.max_entries) {
3925
- errors.push(
3926
- createFulpackError(
3927
- ERROR_CODES.DECOMPRESSION_BOMB,
3928
- `Entry count exceeds maximum (${options.max_entries})`,
3929
- "extract" /* EXTRACT */,
3930
- {
3931
- archive,
3932
- details: { entry_count: entryCount, max_entries: options.max_entries }
3933
- }
3934
- )
3935
- );
3936
- stream.resume();
3937
- next();
3938
- return;
3939
- }
3940
- const pathError = validatePath(header.name, "extract" /* EXTRACT */, false);
3941
- if (pathError) {
3942
- errors.push({ ...pathError, archive });
3943
- errorCount++;
3944
- stream.resume();
3945
- next();
3946
- return;
3947
- }
3948
- const outputPath = join(destination, header.name);
3949
- if (header.type === "directory") {
3950
- if (!existsSync(outputPath)) {
3951
- mkdirSync(outputPath, { recursive: true });
3952
- }
3953
- extractedCount++;
3954
- stream.resume();
3955
- next();
3956
- } else if (header.type === "file") {
3957
- if (existsSync(outputPath)) {
3958
- if (options.overwrite === "error") {
3959
- errors.push(
3960
- createFulpackError(
3961
- ERROR_CODES.EXTRACTION_FAILED,
3962
- `Output file already exists: ${outputPath}`,
3963
- "extract" /* EXTRACT */,
3964
- { path: header.name, archive }
3965
- )
3966
- );
3967
- errorCount++;
3968
- stream.resume();
3969
- next();
3970
- return;
3971
- } else if (options.overwrite === "skip") {
3972
- warnings.push(`Skipped existing file: ${outputPath}`);
3973
- skippedCount++;
3974
- stream.resume();
3975
- next();
3976
- return;
3977
- }
3978
- }
3979
- const parentDir = dirname(outputPath);
3980
- if (!existsSync(parentDir)) {
3981
- mkdirSync(parentDir, { recursive: true });
3982
- }
3983
- const writeStream = createWriteStream(outputPath);
3984
- stream.on("data", (chunk) => {
3985
- totalExtractedSize += chunk.length;
3986
- if (options.max_size && totalExtractedSize > options.max_size) {
3987
- stream.destroy();
3988
- writeStream.destroy();
3989
- errors.push(
3990
- createFulpackError(
3991
- ERROR_CODES.DECOMPRESSION_BOMB,
3992
- `Total extracted size exceeds maximum (${options.max_size} bytes)`,
3993
- "extract" /* EXTRACT */,
3994
- {
3995
- archive,
3996
- path: header.name,
3997
- details: { actual_size: totalExtractedSize, max_size: options.max_size }
3998
- }
3999
- )
4000
- );
4001
- errorCount++;
4002
- next();
4003
- return;
4004
- }
4005
- });
4006
- stream.pipe(writeStream);
4007
- writeStream.on("finish", () => {
4008
- if (options.preserve_permissions && header.mode) {
4009
- try {
4010
- const { chmodSync } = __require("fs");
4011
- chmodSync(outputPath, header.mode);
4012
- } catch {
4013
- }
4014
- }
4015
- extractedCount++;
4016
- next();
4017
- });
4018
- writeStream.on("error", (error) => {
4019
- errors.push(
4020
- createFulpackError(
4021
- ERROR_CODES.EXTRACTION_FAILED,
4022
- `Failed to write file: ${error.message}`,
4023
- "extract" /* EXTRACT */,
4024
- { path: header.name, archive, details: { original_error: error } }
4025
- )
4026
- );
4027
- errorCount++;
4028
- next();
4029
- });
4030
- } else {
4031
- warnings.push(`Skipped entry type ${header.type}: ${header.name}`);
4032
- stream.resume();
4033
- next();
4034
- }
4035
- } catch (error) {
4036
- errors.push(
4037
- createFulpackError(
4038
- ERROR_CODES.EXTRACTION_FAILED,
4039
- `Error processing entry: ${error instanceof Error ? error.message : "Unknown error"}`,
4040
- "extract" /* EXTRACT */,
4041
- { path: header.name, archive, details: { original_error: error } }
4042
- )
4043
- );
4044
- errorCount++;
4045
- stream.resume();
4046
- next();
4047
- }
4048
- });
4049
- const readStream = createReadStream(archive);
4050
- return new Promise((resolve, reject) => {
4051
- extract2.on("finish", () => {
4052
- resolve({
4053
- extracted_count: extractedCount,
4054
- skipped_count: skippedCount,
4055
- error_count: errorCount,
4056
- errors: errors.length > 0 ? errors : void 0,
4057
- warnings: warnings.length > 0 ? warnings : void 0,
4058
- total_bytes: totalExtractedSize
4059
- });
4060
- });
4061
- extract2.on("error", (error) => {
4062
- reject(
4063
- new FulpackOperationError(
4064
- createFulpackError(
4065
- ERROR_CODES.EXTRACTION_FAILED,
4066
- `TAR extraction failed: ${error.message}`,
4067
- "extract" /* EXTRACT */,
4068
- { archive, details: { original_error: error } }
4069
- )
4070
- )
4071
- );
4072
- });
4073
- readStream.pipe(extract2);
4074
- });
4075
- }
4076
- async function extractZip(archive, destination, options) {
4077
- const unzipper = await import('unzipper');
4078
- let extractedCount = 0;
4079
- let skippedCount = 0;
4080
- let errorCount = 0;
4081
- const errors = [];
4082
- const warnings = [];
4083
- let totalExtractedSize = 0;
4084
- let entryCount = 0;
4085
- return new Promise((resolve, reject) => {
4086
- const readStream = createReadStream(archive);
4087
- readStream.pipe(unzipper.Parse()).on("entry", async (entry) => {
4088
- entryCount++;
4089
- try {
4090
- if (options.max_entries && entryCount > options.max_entries) {
4091
- errors.push(
4092
- createFulpackError(
4093
- ERROR_CODES.DECOMPRESSION_BOMB,
4094
- `Entry count exceeds maximum (${options.max_entries})`,
4095
- "extract" /* EXTRACT */,
4096
- {
4097
- archive,
4098
- details: { entry_count: entryCount, max_entries: options.max_entries }
4099
- }
4100
- )
4101
- );
4102
- entry.autodrain();
4103
- return;
4104
- }
4105
- const pathError = validatePath(entry.path, "extract" /* EXTRACT */, false);
4106
- if (pathError) {
4107
- errors.push({ ...pathError, archive });
4108
- errorCount++;
4109
- entry.autodrain();
4110
- return;
4111
- }
4112
- const outputPath = join(destination, entry.path);
4113
- if (entry.type === "Directory") {
4114
- if (!existsSync(outputPath)) {
4115
- mkdirSync(outputPath, { recursive: true });
4116
- }
4117
- extractedCount++;
4118
- entry.autodrain();
4119
- } else if (entry.type === "File") {
4120
- if (existsSync(outputPath)) {
4121
- if (options.overwrite === "error") {
4122
- errors.push(
4123
- createFulpackError(
4124
- ERROR_CODES.EXTRACTION_FAILED,
4125
- `Output file already exists: ${outputPath}`,
4126
- "extract" /* EXTRACT */,
4127
- { path: entry.path, archive }
4128
- )
4129
- );
4130
- errorCount++;
4131
- entry.autodrain();
4132
- return;
4133
- } else if (options.overwrite === "skip") {
4134
- warnings.push(`Skipped existing file: ${outputPath}`);
4135
- skippedCount++;
4136
- entry.autodrain();
4137
- return;
4138
- }
4139
- }
4140
- const parentDir = dirname(outputPath);
4141
- if (!existsSync(parentDir)) {
4142
- mkdirSync(parentDir, { recursive: true });
4143
- }
4144
- const writeStream = createWriteStream(outputPath);
4145
- entry.on("data", (chunk) => {
4146
- totalExtractedSize += chunk.length;
4147
- if (options.max_size && totalExtractedSize > options.max_size) {
4148
- entry.destroy();
4149
- writeStream.destroy();
4150
- errors.push(
4151
- createFulpackError(
4152
- ERROR_CODES.DECOMPRESSION_BOMB,
4153
- `Total extracted size exceeds maximum (${options.max_size} bytes)`,
4154
- "extract" /* EXTRACT */,
4155
- {
4156
- archive,
4157
- path: entry.path,
4158
- details: { actual_size: totalExtractedSize, max_size: options.max_size }
4159
- }
4160
- )
4161
- );
4162
- errorCount++;
4163
- return;
4164
- }
4165
- });
4166
- entry.pipe(writeStream);
4167
- writeStream.on("finish", () => {
4168
- extractedCount++;
4169
- });
4170
- writeStream.on("error", (error) => {
4171
- errors.push(
4172
- createFulpackError(
4173
- ERROR_CODES.EXTRACTION_FAILED,
4174
- `Failed to write file: ${error.message}`,
4175
- "extract" /* EXTRACT */,
4176
- { path: entry.path, archive, details: { original_error: error } }
4177
- )
4178
- );
4179
- errorCount++;
4180
- });
4181
- } else {
4182
- warnings.push(`Skipped entry type ${entry.type}: ${entry.path}`);
4183
- entry.autodrain();
4184
- }
4185
- } catch (error) {
4186
- errors.push(
4187
- createFulpackError(
4188
- ERROR_CODES.EXTRACTION_FAILED,
4189
- `Error processing entry: ${error instanceof Error ? error.message : "Unknown error"}`,
4190
- "extract" /* EXTRACT */,
4191
- { path: entry.path, archive, details: { original_error: error } }
4192
- )
4193
- );
4194
- errorCount++;
4195
- entry.autodrain();
4196
- }
4197
- }).on("close", () => {
4198
- resolve({
4199
- extracted_count: extractedCount,
4200
- skipped_count: skippedCount,
4201
- error_count: errorCount,
4202
- errors: errors.length > 0 ? errors : void 0,
4203
- warnings: warnings.length > 0 ? warnings : void 0,
4204
- total_bytes: totalExtractedSize
4205
- });
4206
- }).on("error", (error) => {
4207
- reject(
4208
- new FulpackOperationError(
4209
- createFulpackError(
4210
- ERROR_CODES.EXTRACTION_FAILED,
4211
- `ZIP extraction failed: ${error.message}`,
4212
- "extract" /* EXTRACT */,
4213
- { archive, details: { original_error: error } }
4214
- )
4215
- )
4216
- );
4217
- });
4218
- });
4219
- }
4220
- async function scanTar(archive, options) {
4221
- const tarStream = await import('tar-stream');
4222
- const extract2 = tarStream.extract();
4223
- const entries = [];
4224
- let entryCount = 0;
4225
- return new Promise((resolve, reject) => {
4226
- extract2.on("entry", async (header, stream, next) => {
4227
- entryCount++;
4228
- if (options.max_entries && entryCount > options.max_entries) {
4229
- stream.resume();
4230
- next();
4231
- reject(
4232
- new FulpackOperationError(
4233
- createFulpackError(
4234
- ERROR_CODES.DECOMPRESSION_BOMB,
4235
- `Entry count exceeds maximum (${options.max_entries})`,
4236
- "scan" /* SCAN */,
4237
- {
4238
- archive,
4239
- details: { entry_count: entryCount, max_entries: options.max_entries }
4240
- }
4241
- )
4242
- )
4243
- );
4244
- return;
4245
- }
4246
- const entry = {
4247
- path: header.name,
4248
- type: header.type === "directory" ? "directory" /* DIRECTORY */ : header.type === "symlink" ? "symlink" /* SYMLINK */ : "file" /* FILE */,
4249
- size: header.size || 0,
4250
- modified: header.mtime ? header.mtime.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
4251
- ...options.include_metadata && header.mode !== void 0 && header.mode !== null ? { mode: `0${(header.mode & 511).toString(8)}` } : {},
4252
- ...options.include_metadata && header.type === "symlink" && header.linkname ? { symlink_target: header.linkname } : {}
4253
- };
4254
- entries.push(entry);
4255
- stream.resume();
4256
- next();
4257
- });
4258
- extract2.on("finish", () => {
4259
- resolve(entries);
4260
- });
4261
- extract2.on("error", (error) => {
4262
- reject(
4263
- new FulpackOperationError(
4264
- createFulpackError(
4265
- ERROR_CODES.ARCHIVE_CORRUPT,
4266
- `TAR scanning failed: ${error.message}`,
4267
- "scan" /* SCAN */,
4268
- { archive, details: { original_error: error } }
4269
- )
4270
- )
4271
- );
4272
- });
4273
- const readStream = createReadStream(archive);
4274
- readStream.pipe(extract2);
4275
- });
4276
- }
4277
- async function scanTarGz(archive, options) {
4278
- const tarStream = await import('tar-stream');
4279
- const extract2 = tarStream.extract();
4280
- const entries = [];
4281
- let entryCount = 0;
4282
- return new Promise((resolve, reject) => {
4283
- extract2.on("entry", async (header, stream, next) => {
4284
- entryCount++;
4285
- if (options.max_entries && entryCount > options.max_entries) {
4286
- stream.resume();
4287
- next();
4288
- reject(
4289
- new FulpackOperationError(
4290
- createFulpackError(
4291
- ERROR_CODES.DECOMPRESSION_BOMB,
4292
- `Entry count exceeds maximum (${options.max_entries})`,
4293
- "scan" /* SCAN */,
4294
- {
4295
- archive,
4296
- details: { entry_count: entryCount, max_entries: options.max_entries }
4297
- }
4298
- )
4299
- )
4300
- );
4301
- return;
4302
- }
4303
- const entry = {
4304
- path: header.name,
4305
- type: header.type === "directory" ? "directory" /* DIRECTORY */ : header.type === "symlink" ? "symlink" /* SYMLINK */ : "file" /* FILE */,
4306
- size: header.size || 0,
4307
- modified: header.mtime ? header.mtime.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
4308
- ...options.include_metadata && header.mode !== void 0 && header.mode !== null ? { mode: `0${(header.mode & 511).toString(8)}` } : {},
4309
- ...options.include_metadata && header.type === "symlink" && header.linkname ? { symlink_target: header.linkname } : {}
4310
- };
4311
- entries.push(entry);
4312
- stream.resume();
4313
- next();
4314
- });
4315
- extract2.on("finish", () => {
4316
- resolve(entries);
4317
- });
4318
- extract2.on("error", (error) => {
4319
- reject(
4320
- new FulpackOperationError(
4321
- createFulpackError(
4322
- ERROR_CODES.ARCHIVE_CORRUPT,
4323
- `TAR.GZ scanning failed: ${error.message}`,
4324
- "scan" /* SCAN */,
4325
- { archive, details: { original_error: error } }
4326
- )
4327
- )
4328
- );
4329
- });
4330
- const readStream = createReadStream(archive);
4331
- const gunzip = createGunzip();
4332
- readStream.pipe(gunzip).pipe(extract2);
4333
- });
4334
- }
4335
- async function scanZip(archive, options) {
4336
- const unzipper = await import('unzipper');
4337
- const entries = [];
4338
- let entryCount = 0;
4339
- return new Promise((resolve, reject) => {
4340
- createReadStream(archive).pipe(unzipper.Parse()).on("entry", (entry) => {
4341
- const typedEntry = entry;
4342
- entryCount++;
4343
- if (options.max_entries && entryCount > options.max_entries) {
4344
- typedEntry.autodrain();
4345
- reject(
4346
- new FulpackOperationError(
4347
- createFulpackError(
4348
- ERROR_CODES.DECOMPRESSION_BOMB,
4349
- `Entry count exceeds maximum (${options.max_entries})`,
4350
- "scan" /* SCAN */,
4351
- {
4352
- archive,
4353
- details: { entry_count: entryCount, max_entries: options.max_entries }
4354
- }
4355
- )
4356
- )
4357
- );
4358
- return;
4359
- }
4360
- const uncompressedSize = typedEntry.vars?.uncompressedSize || typedEntry.props?.size || 0;
4361
- const compressedSize = typedEntry.vars?.compressedSize || typedEntry.props?.compressedSize || 0;
4362
- const archiveEntry = {
4363
- path: typedEntry.path,
4364
- type: typedEntry.type === "Directory" ? "directory" /* DIRECTORY */ : "file" /* FILE */,
4365
- size: uncompressedSize,
4366
- modified: typedEntry.vars?.lastModifiedTime ? new Date(typedEntry.vars.lastModifiedTime).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
4367
- ...options.include_metadata ? {
4368
- compressed_size: compressedSize,
4369
- mode: "0644"
4370
- // ZIP format doesn't preserve Unix permissions consistently
4371
- } : {}
4372
- };
4373
- entries.push(archiveEntry);
4374
- typedEntry.autodrain();
4375
- }).on("error", (error) => {
4376
- reject(
4377
- new FulpackOperationError(
4378
- createFulpackError(
4379
- ERROR_CODES.ARCHIVE_CORRUPT,
4380
- `ZIP scanning failed: ${error.message}`,
4381
- "scan" /* SCAN */,
4382
- { archive, details: { original_error: error } }
4383
- )
4384
- )
4385
- );
4386
- }).on("finish", () => {
4387
- resolve(entries);
4388
- });
4389
- });
4390
- }
4391
-
4392
- // src/fulpack/index.ts
4393
- var FULPACK_VERSION = "1.0.0";
1
+ export { ERROR_CODES, FULPACK_VERSION, FulpackOperationError, checkDecompressionBomb, create, createFulpackError, extract, hasPathTraversal, info, isAbsolutePath, scan, validatePath, verify } from './chunk-P3DNOZOG.js';
2
+ export { buildEnvVar, getBinaryName, getConfigIdentifiers, getConfigName, getEnvPrefix, getEnvVar, getTelemetryNamespace, getVendor } from './chunk-LMNAUTW7.js';
3
+ export { APP_IDENTITY_DIR, APP_IDENTITY_ENV_VAR, APP_IDENTITY_FILENAME, APP_IDENTITY_SCHEMA_ID, MAX_ANCESTOR_SEARCH_DEPTH, clearEmbeddedIdentity, clearIdentityCache, getCachedIdentity, getEmbeddedIdentity, hasEmbeddedIdentity, loadIdentity, registerEmbeddedIdentity } from './chunk-VJIGX7MH.js';
4
+ export { AppIdentityError } from './chunk-WCCMRVZF.js';
5
+ export { buildRuntimeInfo } from './chunk-HY6JQOFN.js';
6
+ export { ArchiveFormat, EntryType, Operation } from './chunk-PQ6TKPUN.js';
7
+ export { DocScribeError, DocScribeParseError, DocScribeUnsupportedFormatError, __internal, detectFormat, extractHeaders, extractMetadata, inspectDocument, normalizeInput, parseFrontmatter, splitDocuments, stripFrontmatter } from './chunk-BCO3FUUO.js';
8
+ import './chunk-GOTEQYAD.js';
9
+ import './chunk-SNV5WG5V.js';
10
+ import './chunk-QCCN6POA.js';
11
+ import './chunk-56F2EEAG.js';
12
+ import './chunk-UP2VWCW5.js';
4394
13
 
4395
14
  // src/index.ts
4396
- var VERSION = "0.3.3";
15
+ var VERSION = "0.4.1";
4397
16
 
4398
- export { APP_IDENTITY_DIR, APP_IDENTITY_ENV_VAR, APP_IDENTITY_FILENAME, APP_IDENTITY_SCHEMA_ID, AppIdentityError, ArchiveFormat, DocScribeError, DocScribeParseError, DocScribeUnsupportedFormatError, ERROR_CODES, EntryType, FULPACK_VERSION, FulpackOperationError, MAX_ANCESTOR_SEARCH_DEPTH, Operation, VERSION, __internal, buildEnvVar, buildRuntimeInfo, checkDecompressionBomb, clearEmbeddedIdentity, clearIdentityCache, create, createFulpackError, detectFormat, extract, extractHeaders, extractMetadata, getBinaryName, getCachedIdentity, getConfigIdentifiers, getConfigName, getEmbeddedIdentity, getEnvPrefix, getEnvVar, getTelemetryNamespace, getVendor, hasEmbeddedIdentity, hasPathTraversal, info, inspectDocument, isAbsolutePath, loadIdentity, normalizeInput, parseFrontmatter, registerEmbeddedIdentity, scan, splitDocuments, stripFrontmatter, validatePath, verify };
17
+ export { VERSION };
4399
18
  //# sourceMappingURL=index.js.map
4400
19
  //# sourceMappingURL=index.js.map