gulp-mu-gulp-api 0.3.1 → 0.3.3

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
@@ -1,8 +1,20 @@
1
1
  # gulp-mu-gulp-api
2
2
 
3
- Public task API of the **µGulp™** orchestrator — progress reporting and interactive dashboard inputs from within gulp tasks.
3
+ <p align="center">
4
+ <a href="https://github.com/mamekudz/microGulp">
5
+ <img src="https://raw.githubusercontent.com/mamekudz/microGulp/main/media/logo_readme.png" alt="µGulp logo" width="180">
6
+ </a>
7
+ </p>
4
8
 
5
- Part of the [µGulp™ project on GitHub](https://github.com/mamekudz/microGulp) (subdirectory [`gulp-mu-gulp-api/`](https://github.com/mamekudz/microGulp/tree/main/gulp-mu-gulp-api)).
9
+ <p align="center">
10
+ <a href="https://github.com/mamekudz/microGulp">
11
+ <img src="https://raw.githubusercontent.com/mamekudz/microGulp/main/media/microgulp-ready.png" alt="µGulp ready" width="150">
12
+ </a>
13
+ </p>
14
+
15
+ Public task API of the **µGulp™** orchestrator — progress reporting, i18x console output and interactive dashboard inputs from within gulp tasks.
16
+
17
+ [![npm version](https://img.shields.io/npm/v/gulp-mu-gulp-api.svg)](https://www.npmjs.com/package/gulp-mu-gulp-api) · [µGulp™ on GitHub](https://github.com/mamekudz/microGulp) · [source tree](https://github.com/mamekudz/microGulp/tree/main/gulp-mu-gulp-api)
6
18
 
7
19
  [English](#english) · [Deutsch](#deutsch)
8
20
 
@@ -20,6 +32,15 @@ The module is deliberately **dependency-free** and knows nothing about µGulp™
20
32
  npm install gulp-mu-gulp-api
21
33
  ```
22
34
 
35
+ ### 0.3.3
36
+
37
+ - **Changed** npm README: centered µGulp logo and µGulp ready badge (absolute raw URLs from the µGulp repo).
38
+
39
+ ### 0.3.2
40
+
41
+ - **Added** `FormatValue()` — renders `<name format="…"/>` placeholders from `<format …>` XML definitions in the consumer project's `i18x/gulp/<lid>.json`.
42
+ - **Changed** built-in format IDs to CamelCase (`floatFix2`, `stdDate`, `progressDuration`, `byteSize`, …); lifecycle duration logs expect raw milliseconds with `format="progressDuration"`.
43
+
23
44
  ### 0.3.0
24
45
 
25
46
  - **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 +104,7 @@ let values = await RequestForm({
83
104
 
84
105
  | Function | Purpose |
85
106
  | :--- | :--- |
86
- | `IsMicroGulp()` / `IsµGulp()` | `true` when the task runs under µGulp™ with an attached dashboard (`IsUGulp` is a deprecated alias) |
107
+ | `IsMicroGulp()` / `IsµGulp()` | `true` when the task runs under µGulp™ with an attached dashboard |
87
108
  | `InstallStringExtensions()` | Installs µLib-compatible `String.prototype.i18xTrans()`/`i18xRegister()` (auto-run on import) |
88
109
  | `Log(text, values?)` / `Warn` / `LogError` | Localized `console.*` via i18x (`<context="task log"/>` etc.) |
89
110
  | `Translate(text, values?)` | Returns the translated string without logging |
@@ -105,8 +126,8 @@ Write the i18xe context tag **into** the source phrase and mark it with `.i18xRe
105
126
 
106
127
  | Placeholder | Source |
107
128
  | :--- | :--- |
108
- | `version` | `µI18nContext.version` export, else `package.json` |
109
- | `project` | `µI18nContext.project` export, else `package.json` `name` |
129
+ | `version` | `µI18xContext.version` export, else `package.json` |
130
+ | `project` | `µI18xContext.project` export, else `package.json` `name` |
110
131
  | `workspace` | workspace folder name |
111
132
  | `source` | gulpfile path relative to the workspace |
112
133
  | `gulpfile` | gulpfile file name |
@@ -116,7 +137,7 @@ Write the i18xe context tag **into** the source phrase and mark it with `.i18xRe
116
137
  Export defaults from the gulpfile (overrides `package.json`):
117
138
 
118
139
  ```javascript
119
- export const µI18nContext = { version: '1.13', project: 'eMP' };
140
+ export const µI18xContext = { version: '1.13', project: 'eMP' };
120
141
 
121
142
  MAKE_BUILDS.µDisplayName = 'Make Build V<version/><context="µDisplayName"/>'.i18xRegister();
122
143
  // => "Make Build V1.13" in the dashboard (translatable per language)
@@ -166,9 +187,9 @@ MIT — © 2026 Meinolf Amekudzi
166
187
 
167
188
  # Deutsch
168
189
 
169
- **gulp-mu-gulp-api** ist die öffentliche Task-API des **µGulp™**-Orchestrators. Gulp-Tasks (und beliebige npm-Pakete innerhalb des Task-Streams) erhalten damit Zugriff auf die erweiterten µGulp™-Dashboard-Funktionen: Fortschrittsanzeige, Texteingaben mit Validierung, Farb- und Schriftart-Auswahl, Einzel- und Mehrfachauswahl, Slider und komplette Formulare.
190
+ **gulp-mu-gulp-api** ist die öffentliche Task-API des **µGulp™**-Orchestrators. Gulp-Tasks (und beliebige npm-Pakete innerhalb des Task-Streams) erhalten damit Zugriff auf die erweiterten µGulp™-Dashboard-Funktionen: Fortschrittsanzeige, i18x-Konsolenausgabe, Texteingaben mit Validierung, Farb- und Schriftart-Auswahl, Einzel- und Mehrfachauswahl, Slider und komplette Formulare.
170
191
 
171
- Teil des [µGulp™-Projekts auf GitHub](https://github.com/mamekudz/microGulp) (Unterverzeichnis [`gulp-mu-gulp-api/`](https://github.com/mamekudz/microGulp/tree/main/gulp-mu-gulp-api)).
192
+ [![npm version](https://img.shields.io/npm/v/gulp-mu-gulp-api.svg)](https://www.npmjs.com/package/gulp-mu-gulp-api) · [µGulp™ auf GitHub](https://github.com/mamekudz/microGulp)
172
193
 
173
194
  Das Modul ist bewusst **abhängigkeitsfrei** und kennt keine µGulp™-Interna: Läuft der Task unter µGulp™, spricht es direkt über den IPC-Kanal des Worker-Prozesses mit der Engine. Läuft das Gulpfile klassisch über die gulp-CLI, degradieren alle Funktionen sauber — Fortschritt landet auf dem Terminal, Eingaben werden per readline abgefragt (TTY) oder mit den deklarierten Default-Werten beantwortet (CI/non-TTY). Tasks bleiben ohne µGulp™ voll lauffähig.
174
195
 
@@ -178,6 +199,15 @@ Das Modul ist bewusst **abhängigkeitsfrei** und kennt keine µGulp™-Interna:
178
199
  npm install gulp-mu-gulp-api
179
200
  ```
180
201
 
202
+ ### 0.3.3
203
+
204
+ - **Geändert** npm-README: zentriertes µGulp-Logo und µGulp-ready-Badge (absolute raw-URLs aus dem µGulp-Repo).
205
+
206
+ ### 0.3.2
207
+
208
+ - **Neu** `FormatValue()` — rendert `<name format="…"/>`-Platzhalter aus `<format …>`-XML-Definitionen in `i18x/gulp/<lid>.json` des Verbraucherprojekts.
209
+ - **Geändert** Format-IDs auf CamelCase (`floatFix2`, `stdDate`, `progressDuration`, `byteSize`, …); Lifecycle-Dauer-Logs erwarten Roh-Millisekunden mit `format="progressDuration"`.
210
+
181
211
  ### 0.3.0
182
212
 
183
213
  - **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 +271,7 @@ let values = await RequestForm({
241
271
 
242
272
  | Funktion | Zweck |
243
273
  | :--- | :--- |
244
- | `IsMicroGulp()` / `IsµGulp()` | `true`, wenn der Task unter µGulp™ mit angebundenem Dashboard läuft (`IsUGulp` ist ein veralteter Alias) |
274
+ | `IsMicroGulp()` / `IsµGulp()` | `true`, wenn der Task unter µGulp™ mit angebundenem Dashboard läuft |
245
275
  | `InstallStringExtensions()` | Installiert µLib-kompatible `String.prototype.i18xTrans()`/`i18xRegister()` (läuft beim Import automatisch) |
246
276
  | `Log(text, values?)` / `Warn` / `LogError` | Lokalisierte `console.*`-Ausgabe über i18x (`<context="task log"/>` usw.) |
247
277
  | `Translate(text, values?)` | Gibt den übersetzten String zurück, ohne zu loggen |
@@ -263,8 +293,8 @@ Den i18xe-Kontext-Tag **in** die Textphrase schreiben und mit `.i18xRegister()`
263
293
 
264
294
  | Platzhalter | Quelle |
265
295
  | :--- | :--- |
266
- | `version` | `µI18nContext.version`-Export, sonst `package.json` |
267
- | `project` | `µI18nContext.project`-Export, sonst `package.json` `name` |
296
+ | `version` | `µI18xContext.version`-Export, sonst `package.json` |
297
+ | `project` | `µI18xContext.project`-Export, sonst `package.json` `name` |
268
298
  | `workspace` | Workspace-Ordnername |
269
299
  | `source` | Gulpfile-Pfad relativ zum Workspace |
270
300
  | `gulpfile` | Gulpfile-Dateiname |
@@ -274,7 +304,7 @@ Den i18xe-Kontext-Tag **in** die Textphrase schreiben und mit `.i18xRegister()`
274
304
  Defaults im Gulpfile exportieren (`package.json` wird überschrieben):
275
305
 
276
306
  ```javascript
277
- export const µI18nContext = { version: '1.13', project: 'eMP' };
307
+ export const µI18xContext = { version: '1.13', project: 'eMP' };
278
308
 
279
309
  MAKE_BUILDS.µDisplayName = 'Make Build V<version/><context="µDisplayName"/>'.i18xRegister();
280
310
  // => „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.3",
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",
@@ -43,6 +43,11 @@
43
43
  "publishConfig": {
44
44
  "access": "public"
45
45
  },
46
+ "microgulp": {
47
+ "ready": true,
48
+ "certifiedSince": "0.3.2",
49
+ "role": "official-task-api"
50
+ },
46
51
  "engines": {
47
52
  "node": ">=18"
48
53
  }
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,