gulp-mu-gulp-api 0.3.1 → 0.3.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.
package/README.md CHANGED
@@ -20,6 +20,11 @@ The module is deliberately **dependency-free** and knows nothing about µGulp™
20
20
  npm install gulp-mu-gulp-api
21
21
  ```
22
22
 
23
+ ### 0.3.2
24
+
25
+ - **Added** `FormatValue()` — renders `<name format="…"/>` placeholders from `<format …>` XML definitions in the consumer project's `i18x/gulp/<lid>.json`.
26
+ - **Changed** built-in format IDs to CamelCase (`floatFix2`, `stdDate`, `progressDuration`, `byteSize`, …); lifecycle duration logs expect raw milliseconds with `format="progressDuration"`.
27
+
23
28
  ### 0.3.0
24
29
 
25
30
  - **Added** integrated i18x console layer: `Log`, `Warn`, `LogError`, `Translate`, `GetLid`, `SetLid`, `GetTimeZone`, `DateInTimeZone`, `InstallStringExtensions` — translations live in the consumer project's `i18x/gulp/<lid>.json`; language follows `MICROGULP_LANG` (set by the µGulp dashboard) with locale fallback.
@@ -83,7 +88,7 @@ let values = await RequestForm({
83
88
 
84
89
  | Function | Purpose |
85
90
  | :--- | :--- |
86
- | `IsMicroGulp()` / `IsµGulp()` | `true` when the task runs under µGulp™ with an attached dashboard (`IsUGulp` is a deprecated alias) |
91
+ | `IsMicroGulp()` / `IsµGulp()` | `true` when the task runs under µGulp™ with an attached dashboard |
87
92
  | `InstallStringExtensions()` | Installs µLib-compatible `String.prototype.i18xTrans()`/`i18xRegister()` (auto-run on import) |
88
93
  | `Log(text, values?)` / `Warn` / `LogError` | Localized `console.*` via i18x (`<context="task log"/>` etc.) |
89
94
  | `Translate(text, values?)` | Returns the translated string without logging |
@@ -105,8 +110,8 @@ Write the i18xe context tag **into** the source phrase and mark it with `.i18xRe
105
110
 
106
111
  | Placeholder | Source |
107
112
  | :--- | :--- |
108
- | `version` | `µI18nContext.version` export, else `package.json` |
109
- | `project` | `µI18nContext.project` export, else `package.json` `name` |
113
+ | `version` | `µI18xContext.version` export, else `package.json` |
114
+ | `project` | `µI18xContext.project` export, else `package.json` `name` |
110
115
  | `workspace` | workspace folder name |
111
116
  | `source` | gulpfile path relative to the workspace |
112
117
  | `gulpfile` | gulpfile file name |
@@ -116,7 +121,7 @@ Write the i18xe context tag **into** the source phrase and mark it with `.i18xRe
116
121
  Export defaults from the gulpfile (overrides `package.json`):
117
122
 
118
123
  ```javascript
119
- export const µI18nContext = { version: '1.13', project: 'eMP' };
124
+ export const µI18xContext = { version: '1.13', project: 'eMP' };
120
125
 
121
126
  MAKE_BUILDS.µDisplayName = 'Make Build V<version/><context="µDisplayName"/>'.i18xRegister();
122
127
  // => "Make Build V1.13" in the dashboard (translatable per language)
@@ -178,6 +183,11 @@ Das Modul ist bewusst **abhängigkeitsfrei** und kennt keine µGulp™-Interna:
178
183
  npm install gulp-mu-gulp-api
179
184
  ```
180
185
 
186
+ ### 0.3.2
187
+
188
+ - **Neu** `FormatValue()` — rendert `<name format="…"/>`-Platzhalter aus `<format …>`-XML-Definitionen in `i18x/gulp/<lid>.json` des Verbraucherprojekts.
189
+ - **Geändert** Format-IDs auf CamelCase (`floatFix2`, `stdDate`, `progressDuration`, `byteSize`, …); Lifecycle-Dauer-Logs erwarten Roh-Millisekunden mit `format="progressDuration"`.
190
+
181
191
  ### 0.3.0
182
192
 
183
193
  - **Neu** integrierte i18x-Konsolenschicht: `Log`, `Warn`, `LogError`, `Translate`, `GetLid`, `SetLid`, `GetTimeZone`, `DateInTimeZone`, `InstallStringExtensions` — Übersetzungen liegen im Verbraucherprojekt unter `i18x/gulp/<lid>.json`; die Sprache folgt `MICROGULP_LANG` (vom µGulp-Dashboard gesetzt) mit Locale-Fallback.
@@ -241,7 +251,7 @@ let values = await RequestForm({
241
251
 
242
252
  | Funktion | Zweck |
243
253
  | :--- | :--- |
244
- | `IsMicroGulp()` / `IsµGulp()` | `true`, wenn der Task unter µGulp™ mit angebundenem Dashboard läuft (`IsUGulp` ist ein veralteter Alias) |
254
+ | `IsMicroGulp()` / `IsµGulp()` | `true`, wenn der Task unter µGulp™ mit angebundenem Dashboard läuft |
245
255
  | `InstallStringExtensions()` | Installiert µLib-kompatible `String.prototype.i18xTrans()`/`i18xRegister()` (läuft beim Import automatisch) |
246
256
  | `Log(text, values?)` / `Warn` / `LogError` | Lokalisierte `console.*`-Ausgabe über i18x (`<context="task log"/>` usw.) |
247
257
  | `Translate(text, values?)` | Gibt den übersetzten String zurück, ohne zu loggen |
@@ -263,8 +273,8 @@ Den i18xe-Kontext-Tag **in** die Textphrase schreiben und mit `.i18xRegister()`
263
273
 
264
274
  | Platzhalter | Quelle |
265
275
  | :--- | :--- |
266
- | `version` | `µI18nContext.version`-Export, sonst `package.json` |
267
- | `project` | `µI18nContext.project`-Export, sonst `package.json` `name` |
276
+ | `version` | `µI18xContext.version`-Export, sonst `package.json` |
277
+ | `project` | `µI18xContext.project`-Export, sonst `package.json` `name` |
268
278
  | `workspace` | Workspace-Ordnername |
269
279
  | `source` | Gulpfile-Pfad relativ zum Workspace |
270
280
  | `gulpfile` | Gulpfile-Dateiname |
@@ -274,7 +284,7 @@ Den i18xe-Kontext-Tag **in** die Textphrase schreiben und mit `.i18xRegister()`
274
284
  Defaults im Gulpfile exportieren (`package.json` wird überschrieben):
275
285
 
276
286
  ```javascript
277
- export const µI18nContext = { version: '1.13', project: 'eMP' };
287
+ export const µI18xContext = { version: '1.13', project: 'eMP' };
278
288
 
279
289
  MAKE_BUILDS.µDisplayName = 'Make Build V<version/><context="µDisplayName"/>'.i18xRegister();
280
290
  // => „Make Build V1.13“ im Dashboard (pro Sprache übersetzbar)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gulp-mu-gulp-api",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Public task API for the µGulp orchestrator: progress reporting, sound signals, speech output, localized console output (i18x) and interactive UI inputs (text, password, number, textarea, color, font, select, radio, multi-select checkbox, range slider, date/time, file) from within gulp tasks — with graceful CLI fallbacks when running without µGulp.",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
package/src/i18x.mjs CHANGED
@@ -50,6 +50,7 @@ const LIDS_MAIN_CULTURES = {
50
50
  };
51
51
 
52
52
  let dictionaryCache = new Map();
53
+ let formatDefinitionCache = new Map();
53
54
  let availableLids = null;
54
55
  let activeLid = null;
55
56
  const timeZoneFormatters = {};
@@ -175,6 +176,7 @@ export function GetLid() {
175
176
  */
176
177
  export function SetLid(_lid) {
177
178
  activeLid = _lid ? _BestAvailableLid(_lid, _AvailableLids()) : null;
179
+ formatDefinitionCache.clear();
178
180
  }
179
181
 
180
182
  /** Loads and caches `<dir>/<lid>.json`; a missing/broken file yields source text. */
@@ -191,29 +193,104 @@ function _LoadDictionary(_lid) {
191
193
  return dictionary;
192
194
  }
193
195
 
194
- /** Substitutes `<name/>` placeholders; honors `format="…"` for int and clock times. */
196
+ /** @param {Record<string, string>} _dictionary */
197
+ function _ExtractFormatDefinitions(_dictionary) {
198
+ let formats = {};
199
+ for (let [key, value] of Object.entries(_dictionary)) {
200
+ if (!key.startsWith('<format name=')) continue;
201
+ let match = key.match(/<format name="([^"]+)"/);
202
+ if (match) formats[match[1]] = value;
203
+ }
204
+ return formats;
205
+ }
206
+
207
+ /** Localized i18xe format XML keyed by format id (from i18x/gulp dictionaries). */
208
+ function _LoadFormatDefinitions(_lid) {
209
+ if (formatDefinitionCache.has(_lid)) return formatDefinitionCache.get(_lid);
210
+ let formats = { ..._ExtractFormatDefinitions(_LoadDictionary(DEFAULT_LID)) };
211
+ if (_lid !== DEFAULT_LID) {
212
+ Object.assign(formats, _ExtractFormatDefinitions(_LoadDictionary(_lid)));
213
+ }
214
+ formatDefinitionCache.set(_lid, formats);
215
+ return formats;
216
+ }
217
+
218
+ /** Formats milliseconds as M:SS or H:MM:SS (progress bar durations). */
219
+ function _FormatDuration(_ms) {
220
+ let ms = Math.max(0, Number(_ms) || 0);
221
+ let totalSeconds = Math.round(ms / 1000);
222
+ let seconds = totalSeconds % 60;
223
+ let minutes = Math.floor(totalSeconds / 60) % 60;
224
+ let hours = Math.floor(totalSeconds / 3600);
225
+ let pad = (_n) => String(_n).padStart(2, '0');
226
+ return hours > 0 ? hours + ':' + pad(minutes) + ':' + pad(seconds) : minutes + ':' + pad(seconds);
227
+ }
228
+
229
+ /**
230
+ * Formats a numeric value with a named i18xe format definition from the
231
+ * project's i18x/gulp dictionaries (synced from i18xe prod).
232
+ * @param {number|string} _value
233
+ * @param {string} _formatName e.g. 'int', 'floatFix2', 'byteSize'
234
+ * @param {string} [_lid]
235
+ * @returns {string}
236
+ */
237
+ export function FormatValue(_value, _formatName, _lid) {
238
+ let lid = _lid ?? GetLid();
239
+ let tz = GetTimeZone();
240
+ if (_formatName === 'progressDuration') return _FormatDuration(Number(_value));
241
+ if (_formatName === 'stdTime') {
242
+ let date = new Date(Number(_value) || 0);
243
+ try {
244
+ return new Intl.DateTimeFormat(lid, { hour: '2-digit', minute: '2-digit', timeZone: tz }).format(date);
245
+ } catch {
246
+ return date.toISOString();
247
+ }
248
+ }
249
+ if (_formatName === 'stdDateTime' || _formatName === 'fullDateTime') {
250
+ let date = new Date(Number(_value) || 0);
251
+ try {
252
+ return new Intl.DateTimeFormat(lid, {
253
+ year: 'numeric', month: '2-digit', day: '2-digit',
254
+ hour: '2-digit', minute: '2-digit', timeZone: tz,
255
+ }).format(date);
256
+ } catch {
257
+ return date.toISOString();
258
+ }
259
+ }
260
+ if (_formatName === 'byteSize') {
261
+ let bytes = Number(_value);
262
+ if (!Number.isFinite(bytes)) return String(_value);
263
+ if (bytes >= 1073741824) return FormatValue(bytes / 1073741824, 'floatFix2', lid) + ' GB';
264
+ if (bytes >= 1048576) return FormatValue(bytes / 1048576, 'floatFix2', lid) + ' MB';
265
+ if (bytes >= 1024) return FormatValue(bytes / 1024, 'floatFix2', lid) + ' KB';
266
+ return FormatValue(bytes, 'int', lid) + ' Bytes';
267
+ }
268
+ let number = Number(_value);
269
+ if (!Number.isFinite(number)) return String(_value);
270
+ let definition = _LoadFormatDefinitions(lid)[_formatName];
271
+ if (!definition) return String(_value);
272
+ let groupSeparator = definition.match(/<xa if="1000\|1000\+">([^<]*)<\/xa>/)?.[1] ?? ',';
273
+ let decimalSeparator = definition.match(/if="0~">([^<]*)<m0\/>/)?.[1]
274
+ ?? definition.match(/<\/xa>([^<]*)<f0\/>/)?.[1] ?? '.';
275
+ let fractionDigits = _formatName === 'floatFix1' ? 1
276
+ : (_formatName.startsWith('floatFix') ? 2 : 0);
277
+ if (_formatName === 'int') number = Math.round(number);
278
+ let negative = number < 0;
279
+ let absolute = Math.abs(number);
280
+ let integerPart = String(Math.trunc(fractionDigits ? absolute : Math.round(absolute)));
281
+ let grouped = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator);
282
+ let fraction = fractionDigits ? decimalSeparator + absolute.toFixed(fractionDigits).split('.')[1] : '';
283
+ return (negative ? '-' : '') + grouped + fraction;
284
+ }
285
+
286
+ /** Substitutes `<name/>` placeholders; honors `format="…"` via FormatValue. */
195
287
  function _ApplyPlaceholders(_text, _values) {
196
288
  let text = String(_text);
289
+ let lid = GetLid();
197
290
  for (let key of Object.keys(_values ?? {})) {
198
291
  let pattern = new RegExp('<' + key + '(?:\\s+format="([^"]*)")?\\s*/>', 'g');
199
- text = text.replace(pattern, (_match, _formatName) => {
200
- if (_formatName === 'int') return String(Math.round(Number(_values[key]) || 0));
201
- if (_formatName === 'stdtime' || _formatName === 'stddatetime') {
202
- let date = new Date(Number(_values[key]) || 0);
203
- let lid = GetLid();
204
- try {
205
- return new Intl.DateTimeFormat(lid, {
206
- ...( _formatName === 'stddatetime'
207
- ? { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }
208
- : { hour: '2-digit', minute: '2-digit' }),
209
- timeZone: GetTimeZone(),
210
- }).format(date);
211
- } catch {
212
- return date.toISOString();
213
- }
214
- }
215
- return String(_values[key]);
216
- });
292
+ text = text.replace(pattern, (_match, _formatName) =>
293
+ _formatName ? FormatValue(_values[key], _formatName, lid) : String(_values[key]));
217
294
  }
218
295
  return text.replace(/<[^>]*>/g, '');
219
296
  }
@@ -302,5 +379,5 @@ InstallStringExtensions();
302
379
 
303
380
  export default {
304
381
  GetLid, SetLid, GetTimeZone, DateInTimeZone,
305
- Translate, Log, Warn, LogError, InstallStringExtensions,
382
+ Translate, FormatValue, Log, Warn, LogError, InstallStringExtensions,
306
383
  };
package/src/index.mjs CHANGED
@@ -30,7 +30,7 @@
30
30
 
31
31
  // Localized console output (i18x). Re-exported so tasks import everything
32
32
  // from one place: `import { Log, Warn, ReportProgress } from 'gulp-mu-gulp-api'`.
33
- export { Log, Warn, LogError, Translate, GetLid, SetLid, GetTimeZone, DateInTimeZone, InstallStringExtensions } from './i18x.mjs';
33
+ export { Log, Warn, LogError, Translate, FormatValue, GetLid, SetLid, GetTimeZone, DateInTimeZone, InstallStringExtensions } from './i18x.mjs';
34
34
  import * as I18x from './i18x.mjs';
35
35
 
36
36
  let uiRequestCounter = 0;
@@ -43,15 +43,12 @@ const pendingUiRequests = new Map();
43
43
  * attached dashboard (progress and inputs are rendered in the webview)
44
44
  */
45
45
  export function IsMicroGulp() {
46
- return typeof process.send === 'function' && process.env.UGULP === '1';
46
+ return typeof process.send === 'function' && process.env.MICROGULP === '1';
47
47
  }
48
48
 
49
49
  /** Brand-aligned alias for {@link IsMicroGulp}. */
50
50
  export const IsµGulp = IsMicroGulp;
51
51
 
52
- /** @deprecated Use {@link IsMicroGulp} or {@link IsµGulp}. */
53
- export const IsUGulp = IsMicroGulp;
54
-
55
52
  /*
56
53
  * Task metadata (µDisplayName, µDescription, µTooltip, µGroup) is localized
57
54
  * the same way as any other i18x phrase: with the context tag written *into*
@@ -520,7 +517,6 @@ function _AsciiGallery(_payload) {
520
517
  export default {
521
518
  IsMicroGulp,
522
519
  IsµGulp,
523
- IsUGulp,
524
520
  ReportProgress,
525
521
  CreateProgress,
526
522
  PlaySignal,