tiny-essentials 1.29.0 → 1.29.2

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 (131) hide show
  1. package/changelog/1/29/1.md +11 -0
  2. package/changelog/1/29/2.md +82 -0
  3. package/dist/v1/TinyBasicsEs.min.js +1 -1
  4. package/dist/v1/TinyEssentials.min.js +1 -1
  5. package/dist/v1/TinyFetchLoader.min.js +1 -0
  6. package/dist/v1/TinyImageLoader.min.js +1 -0
  7. package/dist/v1/TinyMediaCache.min.js +1 -0
  8. package/dist/v1/TinyMediaLoader.min.js +1 -0
  9. package/dist/v1/basics/index.cjs +5 -0
  10. package/dist/v1/basics/index.d.mts +2 -1
  11. package/dist/v1/basics/index.d.mts.map +1 -1
  12. package/dist/v1/basics/index.mjs +2 -1
  13. package/dist/v1/basics/media/youtube.cjs +275 -0
  14. package/dist/v1/basics/media/youtube.d.mts +107 -0
  15. package/dist/v1/basics/media/youtube.d.mts.map +1 -0
  16. package/dist/v1/basics/media/youtube.mjs +214 -0
  17. package/dist/v1/index.cjs +13 -0
  18. package/dist/v1/index.d.mts +6 -1
  19. package/dist/v1/index.d.mts.map +1 -1
  20. package/dist/v1/index.mjs +7 -2
  21. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.cjs +0 -1
  22. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.d.mts +0 -1
  23. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.d.mts.map +1 -1
  24. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.mjs +0 -1
  25. package/dist/v1/libs/fs/plugins/OPFS/index.cjs +0 -8
  26. package/dist/v1/libs/fs/plugins/OPFS/index.d.mts +0 -8
  27. package/dist/v1/libs/fs/plugins/OPFS/index.d.mts.map +1 -1
  28. package/dist/v1/libs/fs/plugins/OPFS/index.mjs +0 -8
  29. package/dist/v1/libs/html/TinyHtml.cjs +1 -1
  30. package/dist/v1/libs/html/TinyHtml.d.mts +2 -2
  31. package/dist/v1/libs/html/TinyHtml.d.mts.map +1 -1
  32. package/dist/v1/libs/html/TinyHtml.mjs +1 -1
  33. package/dist/v1/libs/html/loader/TinyMediaLoader.cjs +1999 -0
  34. package/dist/v1/libs/html/loader/TinyMediaLoader.d.mts +999 -0
  35. package/dist/v1/libs/html/loader/TinyMediaLoader.d.mts.map +1 -0
  36. package/dist/v1/libs/html/loader/TinyMediaLoader.mjs +1852 -0
  37. package/dist/v1/libs/html/loader/beta/formats/TinyAudioLoader.cjs +77 -0
  38. package/dist/v1/libs/html/loader/beta/formats/TinyAudioLoader.d.mts +103 -0
  39. package/dist/v1/libs/html/loader/beta/formats/TinyAudioLoader.d.mts.map +1 -0
  40. package/dist/v1/libs/html/loader/beta/formats/TinyAudioLoader.mjs +67 -0
  41. package/dist/v1/libs/html/loader/beta/formats/TinyContentLoader.cjs +630 -0
  42. package/dist/v1/libs/html/loader/beta/formats/TinyContentLoader.d.mts +138 -0
  43. package/dist/v1/libs/html/loader/beta/formats/TinyContentLoader.d.mts.map +1 -0
  44. package/dist/v1/libs/html/loader/beta/formats/TinyContentLoader.mjs +576 -0
  45. package/dist/v1/libs/html/loader/beta/formats/TinyVideoLoader.cjs +70 -0
  46. package/dist/v1/libs/html/loader/beta/formats/TinyVideoLoader.d.mts +100 -0
  47. package/dist/v1/libs/html/loader/beta/formats/TinyVideoLoader.d.mts.map +1 -0
  48. package/dist/v1/libs/html/loader/beta/formats/TinyVideoLoader.mjs +60 -0
  49. package/dist/v1/libs/html/loader/beta/probe/TinyMediaProbe.cjs +946 -0
  50. package/dist/v1/libs/html/loader/beta/probe/TinyMediaProbe.d.mts +67 -0
  51. package/dist/v1/libs/html/loader/beta/probe/TinyMediaProbe.d.mts.map +1 -0
  52. package/dist/v1/libs/html/loader/beta/probe/TinyMediaProbe.mjs +896 -0
  53. package/dist/v1/libs/html/loader/formats/TinyFetchLoader.cjs +225 -0
  54. package/dist/v1/libs/html/loader/formats/TinyFetchLoader.d.mts +149 -0
  55. package/dist/v1/libs/html/loader/formats/TinyFetchLoader.d.mts.map +1 -0
  56. package/dist/v1/libs/html/loader/formats/TinyFetchLoader.mjs +197 -0
  57. package/dist/v1/libs/html/loader/formats/TinyImageLoader.cjs +1454 -0
  58. package/dist/v1/libs/html/loader/formats/TinyImageLoader.d.mts +200 -0
  59. package/dist/v1/libs/html/loader/formats/TinyImageLoader.d.mts.map +1 -0
  60. package/dist/v1/libs/html/loader/formats/TinyImageLoader.mjs +1317 -0
  61. package/dist/v1/libs/html/loader/probe/TinyImageProbe.cjs +668 -0
  62. package/dist/v1/libs/html/loader/probe/TinyImageProbe.d.mts +83 -0
  63. package/dist/v1/libs/html/loader/probe/TinyImageProbe.d.mts.map +1 -0
  64. package/dist/v1/libs/html/loader/probe/TinyImageProbe.mjs +626 -0
  65. package/dist/v1/libs/html/loader/utils/TinyBufferReader.cjs +277 -0
  66. package/dist/v1/libs/html/loader/utils/TinyBufferReader.d.mts +118 -0
  67. package/dist/v1/libs/html/loader/utils/TinyBufferReader.d.mts.map +1 -0
  68. package/dist/v1/libs/html/loader/utils/TinyBufferReader.mjs +247 -0
  69. package/dist/v1/libs/html/loader/utils/TinyIsoBox.cjs +128 -0
  70. package/dist/v1/libs/html/loader/utils/TinyIsoBox.d.mts +49 -0
  71. package/dist/v1/libs/html/loader/utils/TinyIsoBox.d.mts.map +1 -0
  72. package/dist/v1/libs/html/loader/utils/TinyIsoBox.mjs +116 -0
  73. package/dist/v1/libs/html/loader/utils/TinyMediaCache.cjs +796 -0
  74. package/dist/v1/libs/html/loader/utils/TinyMediaCache.d.mts +354 -0
  75. package/dist/v1/libs/html/loader/utils/TinyMediaCache.d.mts.map +1 -0
  76. package/dist/v1/libs/html/loader/utils/TinyMediaCache.mjs +734 -0
  77. package/dist/v1/libs/html/loader/utils/TinyMediaMirror.cjs +302 -0
  78. package/dist/v1/libs/html/loader/utils/TinyMediaMirror.d.mts +256 -0
  79. package/dist/v1/libs/html/loader/utils/TinyMediaMirror.d.mts.map +1 -0
  80. package/dist/v1/libs/html/loader/utils/TinyMediaMirror.mjs +280 -0
  81. package/dist/v1/libs/html/loader/utils/TinyMediaProgress.cjs +194 -0
  82. package/dist/v1/libs/html/loader/utils/TinyMediaProgress.d.mts +138 -0
  83. package/dist/v1/libs/html/loader/utils/TinyMediaProgress.d.mts.map +1 -0
  84. package/dist/v1/libs/html/loader/utils/TinyMediaProgress.mjs +175 -0
  85. package/dist/v1/libs/tools/TinyPkgExportValidator.cjs +359 -55
  86. package/dist/v1/libs/tools/TinyPkgExportValidator.d.mts +64 -6
  87. package/dist/v1/libs/tools/TinyPkgExportValidator.d.mts.map +1 -1
  88. package/dist/v1/libs/tools/TinyPkgExportValidator.mjs +320 -57
  89. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.cjs +0 -1
  90. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.d.mts +0 -1
  91. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.d.mts.map +1 -1
  92. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.mjs +0 -1
  93. package/dist/v1/webTemplates/bootstrap/5.3/scss/background/_background-grid.scss +15 -0
  94. package/dist/v1/webTemplates/bootstrap/5.3/scss/components/buttons/_buttons-contrast.scss +208 -0
  95. package/dist/v1/webTemplates/bootstrap/5.3/scss/components/buttons/_buttons-ghost.scss +89 -0
  96. package/dist/v1/webTemplates/bootstrap/5.3/scss/components/icon/_cube-icon.scss +65 -0
  97. package/dist/v1/webTemplates/bootstrap/5.3/scss/components/icon/_sphere-pulse.scss +39 -0
  98. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_badges.scss +90 -0
  99. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_bootstrap-overrides.scss +495 -0
  100. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_buttons.scss +390 -0
  101. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_colors.scss +213 -0
  102. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_effects.scss +153 -0
  103. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_forms-tones.scss +69 -0
  104. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_forms.scss +323 -0
  105. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_global.scss +31 -0
  106. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_links.scss +47 -0
  107. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_tables.scss +25 -0
  108. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_theme.scss +21 -0
  109. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_tokens.scss +161 -0
  110. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_utilities.scss +81 -0
  111. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/_variables.scss +63 -0
  112. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/_form-tone-mixins.scss +236 -0
  113. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/_sizing.scss +68 -0
  114. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/_tone-mixins.scss +392 -0
  115. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/_typography.scss +112 -0
  116. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/background/_tiny_gradient_1.scss +131 -0
  117. package/dist/v1/webTemplates/bootstrap/5.3/scss/themes/deepstrap/plugins/colors/_pink.scss +47 -0
  118. package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.cjs +2 -2
  119. package/docs/v1/Exports.md +33 -2
  120. package/docs/v1/README.md +9 -0
  121. package/docs/v1/basics/youtube.md +204 -0
  122. package/docs/v1/libs/html/loader/TinyFetchLoader.md +114 -0
  123. package/docs/v1/libs/html/loader/TinyImageLoader.md +141 -0
  124. package/docs/v1/libs/html/loader/TinyMediaCache.md +166 -0
  125. package/docs/v1/libs/html/loader/TinyMediaLoader.md +300 -0
  126. package/docs/v1/libs/tools/TinyPkgExportValidator.md +55 -0
  127. package/docs/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters.md +0 -4
  128. package/docs/v1/webTemplates/bootstrap/5.3/DeepStrap.md +177 -0
  129. package/package.json +281 -17
  130. /package/dist/v1/webTemplates/bootstrap/5.3/scss/input/{Placeholder-Colors-Fix.scss → _placeholder-colors-fix.scss} +0 -0
  131. /package/dist/v1/webTemplates/bootstrap/5.3/scss/spacing/{Spacing-Plus.scss → _spacing-plus.scss} +0 -0
@@ -23,17 +23,47 @@ var path = require('path');
23
23
  /**
24
24
  * @typedef {Object} DefaultMessages
25
25
  * @property {string} header
26
- * @property {string} divider
27
- * @property {string} itemValid
28
- * @property {string} itemInvalid
29
- * @property {string} itemMissing
30
- * @property {string} successHeader
31
- * @property {string} failureHeader
32
- * @property {string} errorLoad
33
- * @property {string} noExports
34
- * @property {string} errorNotFound
35
- * @property {string} errorParse
36
- * @property {string} errorUnexpected
26
+ * @property {string} divider
27
+ * @property {string} itemValid
28
+ * @property {string} itemInvalid
29
+ * @property {string} itemMissing
30
+ * @property {string} itemSkipped
31
+ * @property {string} itemPattern
32
+ * @property {string} itemInvalidTarget
33
+ * @property {string} successHeader
34
+ * @property {string} failureHeader
35
+ * @property {string} errorLoad
36
+ * @property {string} noExports
37
+ * @property {string} errorNotFound
38
+ * @property {string} errorParse
39
+ * @property {string} errorUnexpected
40
+ */
41
+
42
+ /**
43
+ * @typedef {Object} LogContext
44
+ * @property {string} [projectName] - The project name injected automatically.
45
+ * @property {string} [path] - The dotted context path of the export.
46
+ * @property {string} [errorPath] - The raw target string from package.json.
47
+ * @property {string} [target] - The invalid target value.
48
+ * @property {string} [reason] - The machine-readable failure reason.
49
+ * @property {string} [pattern] - The wildcard pattern that matched.
50
+ * @property {string} [message] - A generic message payload.
51
+ * @property {number} [errorCount] - The number of failures found.
52
+ */
53
+
54
+ /**
55
+ * The kind of target that was validated.
56
+ * @typedef {'file'|'pattern'|'null'|'invalid'} ExportTargetKind
57
+ */
58
+
59
+ /**
60
+ * The validation result of a single export target.
61
+ * @typedef {Object} ExportTargetResult
62
+ * @property {string} path - The dotted context path (e.g. "exports -> . -> import").
63
+ * @property {boolean} valid - Whether the target resolves to an existing file.
64
+ * @property {ExportTargetKind} [kind] - The kind of target that was validated.
65
+ * @property {string} [errorPath] - The raw target string declared in package.json.
66
+ * @property {string} [reason] - A short machine-readable reason code for failures.
37
67
  */
38
68
 
39
69
  /**
@@ -47,6 +77,9 @@ const DEFAULT_MESSAGES = {
47
77
  itemValid: ` [✔] {path}`,
48
78
  itemInvalid: ` [✘] {path}`,
49
79
  itemMissing: ` Missing: {errorPath}`,
80
+ itemSkipped: ` [-] {path} (excluded by null target)`,
81
+ itemPattern: ` [✔] {path} (pattern: {pattern})`,
82
+ itemInvalidTarget: ` Invalid target: {target} ({reason})`,
50
83
  successHeader: `SUCCESS: All exports are correctly mapped.`,
51
84
  failureHeader: `FAILURE: {errorCount} export(s) are missing or invalid.`,
52
85
  errorLoad: `Error: package.json not loaded in the current directory.`,
@@ -110,9 +143,10 @@ class TinyPkgExportValidator {
110
143
  }
111
144
 
112
145
  // 3. Validate that all values are strings
146
+ const colorRecord = /** @type {Record<string, string>} */ (newColors);
147
+
113
148
  for (const key of originalKeys) {
114
- // @ts-ignore
115
- if (typeof newColors[key] !== 'string') {
149
+ if (typeof colorRecord[key] !== 'string') {
116
150
  throw new TypeError(`The value for key "${key}" must be a string.`);
117
151
  }
118
152
  }
@@ -132,7 +166,7 @@ class TinyPkgExportValidator {
132
166
  /** @type {string} */
133
167
  #rootDir;
134
168
 
135
- /** @type {Array<{path: string, valid: boolean, errorPath?: string}>} */
169
+ /** @type {ExportTargetResult[]} */
136
170
  #results;
137
171
 
138
172
  /** @type {boolean} */
@@ -231,9 +265,10 @@ class TinyPkgExportValidator {
231
265
  }
232
266
 
233
267
  // Ensure all required keys from DefaultMessages are present and are strings
268
+ const messages = /** @type {Record<string, unknown>} */ (options.messages);
269
+
234
270
  for (const key of MESSAGE_KEYS) {
235
- // @ts-ignore
236
- if (typeof options.messages[key] !== 'string') {
271
+ if (typeof messages[key] !== 'string') {
237
272
  throw new TypeError(`The "options.messages.${key}" property must be a string.`);
238
273
  }
239
274
  }
@@ -242,8 +277,9 @@ class TinyPkgExportValidator {
242
277
 
243
278
  /**
244
279
  * Internal helper to apply template replacements.
280
+ *
245
281
  * @param {string} template - The message template string.
246
- * @param {Object} context - Data for placeholders.
282
+ * @param {LogContext} context - Data for placeholders.
247
283
  * @returns {string} The processed string.
248
284
  * @throws {TypeError} If template is not a string or context is not an object.
249
285
  */
@@ -255,17 +291,21 @@ class TinyPkgExportValidator {
255
291
  throw new TypeError('The context must be a non-null object.');
256
292
  }
257
293
 
294
+ const values = /** @type {Record<string, unknown>} */ (context);
295
+
258
296
  return template.replace(/{(\w+)}/g, (match, key) => {
259
- // @ts-ignore
260
- return context[key] !== undefined ? context[key] : match;
297
+ const value = values[key];
298
+ return value !== undefined ? String(value) : match;
261
299
  });
262
300
  }
263
301
 
264
302
  /**
265
303
  * Internal helper to handle console output, coloring, and silent mode.
304
+ *
266
305
  * @param {'error'|'warning'|'success'|'info'|'dim'|'cyan'} type - The log type.
267
306
  * @param {string} templateKey - The key in the messages object.
268
- * @param {Object} [context={}] - Data for the template.
307
+ * @param {LogContext} [context={}] - Data for the template.
308
+ * @returns {void}
269
309
  * @throws {TypeError} If arguments do not match required types or if type is invalid.
270
310
  */
271
311
  #log(type, templateKey, context = {}) {
@@ -285,9 +325,8 @@ class TinyPkgExportValidator {
285
325
  throw new TypeError('The context must be a non-null object.');
286
326
  }
287
327
 
288
- const template =
289
- // @ts-ignore
290
- this.#messages[templateKey] || templateKey;
328
+ const messages = /** @type {Record<string, string>} */ (this.#messages);
329
+ const template = messages[templateKey] ?? templateKey;
291
330
  const message = this.#applyTemplate(template, { ...context, projectName: this.#projectName });
292
331
 
293
332
  switch (type) {
@@ -386,24 +425,30 @@ class TinyPkgExportValidator {
386
425
 
387
426
  /**
388
427
  * Executes the validation process.
389
- * @returns {Promise<boolean>} Returns true if all paths are valid, false otherwise.
428
+ *
429
+ * @returns {Promise<boolean>} True if all paths are valid, false otherwise.
430
+ * @throws {TypeError} If the "exports" field mixes subpath keys with condition keys.
390
431
  */
391
432
  async validate() {
392
433
  if (!this.#started) {
393
434
  this.#log('error', 'errorLoad');
394
- return true;
435
+ return false;
395
436
  }
396
437
 
397
438
  const exports = this.#packageData.exports;
398
439
 
399
- if (!exports) {
440
+ if (exports === undefined || exports === null) {
400
441
  this.#log('warning', 'noExports');
401
442
  return true;
402
443
  }
403
444
 
404
445
  this.#results = [];
446
+
405
447
  try {
406
- await this.#traverseExports(exports);
448
+ if (typeof exports === 'object' && !Array.isArray(exports)) {
449
+ this.#assertRootKeys(exports);
450
+ }
451
+ await this.#traverseExports(exports, 'exports', true);
407
452
  } catch (error) {
408
453
  if (error instanceof Error) {
409
454
  if (error instanceof SyntaxError) {
@@ -416,39 +461,264 @@ class TinyPkgExportValidator {
416
461
  }
417
462
  throw error;
418
463
  }
464
+
419
465
  return this.#report();
420
466
  }
421
467
 
422
468
  /**
423
- * Recursively traverses the exports object.
469
+ * Ensures the root "exports" object does not mix subpath keys with condition keys.
470
+ *
471
+ * @param {Record<string, unknown>} exportsObject - The root "exports" object.
472
+ * @returns {void}
473
+ * @throws {TypeError} If subpath keys and condition keys are mixed.
474
+ */
475
+ #assertRootKeys(exportsObject) {
476
+ const keys = Object.keys(exportsObject);
477
+ const subpathCount = keys.filter((key) => key.startsWith('.')).length;
478
+
479
+ if (subpathCount === 0 || subpathCount === keys.length) return;
480
+
481
+ throw new TypeError(
482
+ 'The "exports" field cannot mix subpath keys ("./x") with condition keys ("import").',
483
+ );
484
+ }
485
+
486
+ /**
487
+ * Validates a single key of an exports object.
424
488
  *
425
- * @param {Record<string, any>|string} node - The current node of the exports object.
426
- * @param {string} currentKey - The key (used for log tracking).
489
+ * @param {string} key - The object key to validate.
490
+ * @param {boolean} isRoot - Whether the key belongs to the root "exports" object.
491
+ * @returns {string|null} A reason code when invalid, or null when the key is valid.
492
+ */
493
+ #getKeyError(key, isRoot) {
494
+ if (key.length === 0) return 'EMPTY_KEY';
495
+
496
+ const isSubpath = key === '.' || key.startsWith('./');
497
+
498
+ if (isSubpath) {
499
+ if (!isRoot) return 'NESTED_SUBPATH_KEY';
500
+ return null;
501
+ }
502
+
503
+ if (key.startsWith('.')) return 'INVALID_SUBPATH_KEY';
504
+ if (key.includes(',') || key.includes('/') || key.includes('\\')) return 'INVALID_CONDITION';
505
+
506
+ return null;
507
+ }
508
+
509
+ /**
510
+ * Recursively traverses the exports tree.
511
+ *
512
+ * @param {unknown} node - The current node of the exports tree.
513
+ * @param {string} currentKey - The dotted context path used for reporting.
514
+ * @param {boolean} [isRoot=false] - Whether this node is the root "exports" value.
427
515
  * @returns {Promise<void>}
428
- * @throws {TypeError} If node is not a string or object, or if currentKey is not a string.
516
+ * @throws {TypeError} If currentKey is not a string or node has an unsupported type.
429
517
  */
430
- async #traverseExports(node, currentKey = '') {
518
+ async #traverseExports(node, currentKey, isRoot = false) {
431
519
  if (typeof currentKey !== 'string') {
432
520
  throw new TypeError('The "currentKey" argument must be a string.');
433
521
  }
434
522
 
523
+ // Rule: null is a valid target. It excludes a subpath from a broader pattern.
524
+ if (node === null) {
525
+ this.#results.push({ path: currentKey, valid: true, kind: 'null' });
526
+ return;
527
+ }
528
+
529
+ // Rule: a string is a direct file target.
435
530
  if (typeof node === 'string') {
436
- // Base case: the value is a direct path
437
- await this.#checkFileExists(node, currentKey);
438
- } else if (typeof node === 'object' && node !== null) {
439
- // Recursive case: the value is an object (e.g., { import: '...', require: '...' })
440
- const keys = Object.keys(node);
531
+ await this.#checkTarget(node, currentKey);
532
+ return;
533
+ }
534
+
535
+ // Rule: an array is a fallback list of targets.
536
+ if (Array.isArray(node)) {
537
+ if (node.length === 0) {
538
+ this.#results.push({
539
+ path: currentKey,
540
+ valid: false,
541
+ kind: 'invalid',
542
+ reason: 'EMPTY_ARRAY',
543
+ });
544
+ return;
545
+ }
546
+
547
+ for (let index = 0; index < node.length; index += 1) {
548
+ await this.#traverseExports(node[index], `${currentKey}[${index}]`);
549
+ }
550
+ return;
551
+ }
552
+
553
+ // Rule: an object holds subpaths (root only) or conditions.
554
+ if (typeof node === 'object') {
555
+ const record = /** @type {Record<string, unknown>} */ (node);
556
+ const keys = Object.keys(record);
557
+
558
+ if (keys.length === 0) {
559
+ this.#results.push({
560
+ path: currentKey,
561
+ valid: false,
562
+ kind: 'invalid',
563
+ reason: 'EMPTY_OBJECT',
564
+ });
565
+ return;
566
+ }
567
+
441
568
  for (const key of keys) {
442
- const subKey = currentKey ? `${currentKey} -> ${key}` : key;
443
- await this.#traverseExports(node[key], subKey);
569
+ const keyError = this.#getKeyError(key, isRoot);
570
+
571
+ if (keyError !== null) {
572
+ this.#results.push({
573
+ path: `${currentKey} -> ${key}`,
574
+ valid: false,
575
+ kind: 'invalid',
576
+ errorPath: key,
577
+ reason: keyError,
578
+ });
579
+ continue;
580
+ }
581
+
582
+ await this.#traverseExports(record[key], `${currentKey} -> ${key}`);
583
+ }
584
+ return;
585
+ }
586
+
587
+ throw new TypeError('The "node" argument must be a string, an array, or a non-null object.');
588
+ }
589
+
590
+ /**
591
+ * Validates a single string target and dispatches it to the correct checker.
592
+ *
593
+ * @param {string} target - The raw target string from package.json.
594
+ * @param {string} context - The dotted context path used for reporting.
595
+ * @returns {Promise<void>}
596
+ */
597
+ async #checkTarget(target, context) {
598
+ const targetError = this.#getTargetError(target);
599
+
600
+ if (targetError !== null) {
601
+ this.#results.push({
602
+ path: context,
603
+ valid: false,
604
+ kind: 'invalid',
605
+ errorPath: target,
606
+ reason: targetError,
607
+ });
608
+ return;
609
+ }
610
+
611
+ if (target.includes('*')) {
612
+ await this.#checkPatternTarget(target, context);
613
+ return;
614
+ }
615
+
616
+ await this.#checkFileExists(target, context);
617
+ }
618
+
619
+ /**
620
+ * Applies the path rules documented by Node.js for export targets.
621
+ *
622
+ * @param {string} target - The raw target string from package.json.
623
+ * @returns {string|null} A reason code when invalid, or null when the target is valid.
624
+ */
625
+ #getTargetError(target) {
626
+ if (target.length === 0) return 'EMPTY_TARGET';
627
+ if (target.includes('\\')) return 'INVALID_SEGMENT';
628
+ if (target.includes('?') || target.includes('#')) return 'INVALID_CHARACTER';
629
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(target)) return 'URL_SCHEME_NOT_ALLOWED';
630
+ if (!target.startsWith('./')) return 'TARGET_NOT_RELATIVE';
631
+ if (target.includes('**')) return 'INVALID_PATTERN';
632
+
633
+ const segments = target.slice(2).split('/');
634
+
635
+ for (const segment of segments) {
636
+ const decoded = this.#safeDecode(segment).toLowerCase();
637
+
638
+ if (decoded.length === 0) return 'INVALID_SEGMENT';
639
+ if (decoded === '.' || decoded === '..' || decoded === 'node_modules') {
640
+ return 'INVALID_SEGMENT';
444
641
  }
445
- } else {
446
- throw new TypeError('The "node" argument must be a string or a non-null object.');
447
642
  }
643
+
644
+ return null;
645
+ }
646
+
647
+ /**
648
+ * Decodes a URI component without throwing on malformed input.
649
+ *
650
+ * @param {string} value - The value to decode.
651
+ * @returns {string} The decoded value, or the original value when decoding fails.
652
+ */
653
+ #safeDecode(value) {
654
+ try {
655
+ return decodeURIComponent(value);
656
+ } catch {
657
+ return value;
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Converts a subpath pattern into an anchored regular expression.
663
+ *
664
+ * @param {string} pattern - The file name pattern (e.g. "*.js").
665
+ * @returns {RegExp} The anchored regular expression.
666
+ */
667
+ #patternToRegExp(pattern) {
668
+ const source = pattern
669
+ .split('*')
670
+ .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
671
+ .join('.*');
672
+
673
+ return new RegExp(`^${source}$`);
448
674
  }
449
675
 
450
676
  /**
451
- * Checks for the existence of a file in the file system.
677
+ * Expands a wildcard target against the file system.
678
+ *
679
+ * @param {string} target - The raw target containing at least one "*".
680
+ * @param {string} context - The dotted context path used for reporting.
681
+ * @returns {Promise<void>}
682
+ */
683
+ async #checkPatternTarget(target, context) {
684
+ const lastSlash = target.lastIndexOf('/');
685
+ const directory = lastSlash === 0 ? '.' : target.slice(0, lastSlash);
686
+ const filePattern = target.slice(lastSlash + 1);
687
+
688
+ // When the wildcard lives in a directory segment, fall back to the static prefix.
689
+ const staticDirectory = directory.includes('*')
690
+ ? directory.slice(0, directory.indexOf('*')).replace(/\/[^/]*$/, '') || '.'
691
+ : directory;
692
+
693
+ let entries;
694
+
695
+ try {
696
+ entries = await promises.readdir(path.resolve(this.#rootDir, staticDirectory));
697
+ } catch {
698
+ this.#results.push({
699
+ path: context,
700
+ valid: false,
701
+ kind: 'invalid',
702
+ errorPath: target,
703
+ reason: 'DIRECTORY_NOT_FOUND',
704
+ });
705
+ return;
706
+ }
707
+
708
+ const matcher = this.#patternToRegExp(filePattern);
709
+ const hasMatch = entries.some((entry) => matcher.test(entry));
710
+
711
+ this.#results.push({
712
+ path: context,
713
+ valid: hasMatch,
714
+ kind: hasMatch ? 'pattern' : 'invalid',
715
+ errorPath: target,
716
+ reason: hasMatch ? undefined : 'PATTERN_NO_MATCH',
717
+ });
718
+ }
719
+
720
+ /**
721
+ * Checks for the existence of a regular file in the file system.
452
722
  *
453
723
  * @param {string} relativePath - The relative path defined in package.json.
454
724
  * @param {string} context - The key context for reporting purposes.
@@ -463,15 +733,31 @@ class TinyPkgExportValidator {
463
733
  throw new TypeError('The "context" argument must be a string.');
464
734
  }
465
735
 
466
- // Remove potential wildcards (e.g., '*') for literal path validation
467
- const cleanPath = relativePath.replace(/\*/g, '');
468
- const absolutePath = path.resolve(this.#rootDir, cleanPath);
736
+ const absolutePath = path.resolve(this.#rootDir, relativePath);
469
737
 
470
738
  try {
471
- await promises.access(absolutePath);
472
- this.#results.push({ path: context, valid: true });
739
+ const stats = await promises.stat(absolutePath);
740
+
741
+ if (!stats.isFile()) {
742
+ this.#results.push({
743
+ path: context,
744
+ valid: false,
745
+ kind: 'invalid',
746
+ errorPath: relativePath,
747
+ reason: 'NOT_A_FILE',
748
+ });
749
+ return;
750
+ }
751
+
752
+ this.#results.push({ path: context, valid: true, kind: 'file' });
473
753
  } catch {
474
- this.#results.push({ path: context, valid: false, errorPath: relativePath });
754
+ this.#results.push({
755
+ path: context,
756
+ valid: false,
757
+ kind: 'invalid',
758
+ errorPath: relativePath,
759
+ reason: 'FILE_NOT_FOUND',
760
+ });
475
761
  }
476
762
  }
477
763
 
@@ -486,13 +772,31 @@ class TinyPkgExportValidator {
486
772
  let errorCount = 0;
487
773
 
488
774
  for (const result of this.#results) {
775
+ if (result.kind === 'null') {
776
+ this.#log('dim', 'itemSkipped', { path: result.path });
777
+ continue;
778
+ }
779
+
489
780
  if (result.valid) {
490
- this.#log('success', 'itemValid', { path: result.path });
491
- } else {
492
- this.#log('error', 'itemInvalid', { path: result.path });
781
+ this.#log('success', result.kind === 'pattern' ? 'itemPattern' : 'itemValid', {
782
+ path: result.path,
783
+ pattern: result.errorPath ?? '',
784
+ });
785
+ continue;
786
+ }
787
+
788
+ this.#log('error', 'itemInvalid', { path: result.path });
789
+
790
+ if (result.reason === 'FILE_NOT_FOUND') {
493
791
  this.#log('dim', 'itemMissing', { errorPath: result.errorPath });
494
- errorCount++;
792
+ } else {
793
+ this.#log('dim', 'itemInvalidTarget', {
794
+ target: result.errorPath ?? result.path,
795
+ reason: result.reason ?? 'UNKNOWN',
796
+ });
495
797
  }
798
+
799
+ errorCount++;
496
800
  }
497
801
 
498
802
  this.#log('cyan', 'divider');
@@ -500,10 +804,10 @@ class TinyPkgExportValidator {
500
804
  if (errorCount === 0) {
501
805
  this.#log('success', 'successHeader');
502
806
  return true;
503
- } else {
504
- this.#log('error', 'failureHeader', { errorCount });
505
- return false;
506
807
  }
808
+
809
+ this.#log('error', 'failureHeader', { errorCount });
810
+ return false;
507
811
  }
508
812
  }
509
813
 
@@ -44,6 +44,9 @@ export type DefaultMessages = {
44
44
  itemValid: string;
45
45
  itemInvalid: string;
46
46
  itemMissing: string;
47
+ itemSkipped: string;
48
+ itemPattern: string;
49
+ itemInvalidTarget: string;
47
50
  successHeader: string;
48
51
  failureHeader: string;
49
52
  errorLoad: string;
@@ -52,6 +55,63 @@ export type DefaultMessages = {
52
55
  errorParse: string;
53
56
  errorUnexpected: string;
54
57
  };
58
+ export type LogContext = {
59
+ /**
60
+ * - The project name injected automatically.
61
+ */
62
+ projectName?: string;
63
+ /**
64
+ * - The dotted context path of the export.
65
+ */
66
+ path?: string;
67
+ /**
68
+ * - The raw target string from package.json.
69
+ */
70
+ errorPath?: string;
71
+ /**
72
+ * - The invalid target value.
73
+ */
74
+ target?: string;
75
+ /**
76
+ * - The machine-readable failure reason.
77
+ */
78
+ reason?: string;
79
+ /**
80
+ * - The wildcard pattern that matched.
81
+ */
82
+ pattern?: string;
83
+ /**
84
+ * - A generic message payload.
85
+ */
86
+ message?: string;
87
+ /**
88
+ * - The number of failures found.
89
+ */
90
+ errorCount?: number;
91
+ };
92
+ export type ExportTargetKind = 'file' | 'pattern' | 'null' | 'invalid';
93
+ export type ExportTargetResult = {
94
+ /**
95
+ * - The dotted context path (e.g. "exports -> . -> import").
96
+ */
97
+ path: string;
98
+ /**
99
+ * - Whether the target resolves to an existing file.
100
+ */
101
+ valid: boolean;
102
+ /**
103
+ * - The kind of target that was validated.
104
+ */
105
+ kind?: ExportTargetKind;
106
+ /**
107
+ * - The raw target string declared in package.json.
108
+ */
109
+ errorPath?: string;
110
+ /**
111
+ * - A short machine-readable reason code for failures.
112
+ */
113
+ reason?: string;
114
+ };
55
115
  /**
56
116
  * Class responsible for validating package exports.
57
117
  * Implements state encapsulation, custom logging, and recursive search logic.
@@ -69,11 +129,7 @@ declare class TinyPkgExportValidator {
69
129
  get packageData(): Record<string, any>;
70
130
  get packageJsonPath(): string;
71
131
  get rootDir(): string;
72
- get results(): {
73
- path: string;
74
- valid: boolean;
75
- errorPath?: string;
76
- }[];
132
+ get results(): ExportTargetResult[];
77
133
  get started(): boolean;
78
134
  get projectName(): string;
79
135
  get silent(): boolean;
@@ -110,7 +166,9 @@ declare class TinyPkgExportValidator {
110
166
  start(): Promise<void>;
111
167
  /**
112
168
  * Executes the validation process.
113
- * @returns {Promise<boolean>} Returns true if all paths are valid, false otherwise.
169
+ *
170
+ * @returns {Promise<boolean>} True if all paths are valid, false otherwise.
171
+ * @throws {TypeError} If the "exports" field mixes subpath keys with condition keys.
114
172
  */
115
173
  validate(): Promise<boolean>;
116
174
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TinyPkgExportValidator.d.mts","sourceRoot":"","sources":["../../../../src/v1/libs/tools/TinyPkgExportValidator.mjs"],"names":[],"mappings":"AAIG,YAAkB,aAAa,GAC/B;;;;IAAmB,KAAK,EAAb,MAAM,CACjB;;;;IAAmB,KAAK,EAAb,MAAM,CACjB;;;;IAAmB,GAAG,EAAX,MAAM,CACjB;;;;IAAmB,MAAM,EAAd,MAAM,CACjB;;;;IAAmB,IAAI,EAAZ,MAAM,CACjB;;;;IAAmB,GAAG,EAAX,MAAM,CACnB;CAAA,CAAA;AAGE,YAAkB,gBAAgB,GAClC;;;;IAAoB,WAAW,AAA/B,CACA,EADW,MAAM,CACjB;;;;IAAqB,MAAM,AAA3B,CACA,EADW,OAAO,CAClB;;;;IAA6B,QAAQ,AAArC,CACF,EADa,eAAe,CAC5B;CAAA,CAAA;AAGE,YAAkB,eAAe,GACjC;IAAmB,MAAM,EAAd,MAAM,CACjB;IAAoB,OAAO,EAAhB,MAAM,CACjB;IAAoB,SAAS,EAAlB,MAAM,CACjB;IAAoB,WAAW,EAApB,MAAM,CACjB;IAAoB,WAAW,EAApB,MAAM,CACjB;IAAoB,aAAa,EAAtB,MAAM,CACjB;IAAoB,aAAa,EAAtB,MAAM,CACjB;IAAoB,SAAS,EAAlB,MAAM,CACjB;IAAoB,SAAS,EAAlB,MAAM,CACjB;IAAoB,aAAa,EAAtB,MAAM,CACjB;IAAoB,UAAU,EAAnB,MAAM,CACjB;IAAoB,eAAe,EAAxB,MAAM,CACnB;CAAA,CAAA;AA4BD;;;GAGG;AACH,cAAM,sBAAsB;;IAc1B,IAAI,MAAM,IAOC,aAAa,CALvB;IAED;;;;;OAKG;IACH,IAAI,MAAM,CAAC,SAAS,EAHT,aAGS,EA6BnB;IA8BD,IAAI,WAAW,wBAEd;IACD,IAAI,eAAe,WAElB;IACD,IAAI,OAAO,WAEV;IACD,IAAI,OAAO;cA1Ba,MAAM;eAAS,OAAO;oBAAc,MAAM;QA4BjE;IACD,IAAI,OAAO,YAEV;IACD,IAAI,WAAW,WAEd;IACD,IAAI,MAAM,YAET;IAED;;;;;;;OAOG;IACH,YAAY,eAAe,EALhB,MAKgB,EAAE,OAAO,EAJzB,MAIyB,EAAE,OAAO,AAH1C,CACA,EADQ,gBAGuC,EAkBjD;IAkHD;;;;;;;;;;;;OAYG;IACG,iBAAiB,CAAC,OAAO,EANpB,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAMrB,EAAE,IAAI,EAJ1B,MAAM,EAIoB,iBAiCpC;IAED;;;;;;OAMG;IACG,KAAK,IAJE,OAAO,CAAC,IAAI,CAAC,CAoBzB;IAED;;;OAGG;IACG,QAAQ,IAFD,OAAO,CAAC,OAAO,CAAC,CA+B5B;CAwFF;eAEc,sBAAsB"}
1
+ {"version":3,"file":"TinyPkgExportValidator.d.mts","sourceRoot":"","sources":["../../../../src/v1/libs/tools/TinyPkgExportValidator.mjs"],"names":[],"mappings":"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"}