@qlover/scripts-context 2.1.1 → 2.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/dist/index.js CHANGED
@@ -12,21 +12,23 @@ var defaultFeConfig = {
12
12
  commitlint: {
13
13
  extends: ["@commitlint/config-conventional"]
14
14
  },
15
- release: {
16
- publishPath: "",
17
- autoMergeReleasePR: false,
18
- autoMergeType: "squash",
19
- branchName: "release-${pkgName}-${tagName}",
20
- PRTitle: "[${pkgName} Release] Branch:${branch}, Tag:${tagName}, Env:${env}",
21
- PRBody: "## Publish Details\n\n- \u{1F3F7}\uFE0F Version: ${tagName}\n- \u{1F332} Branch: ${branch}\n- \u{1F527} Environment: ${env}\n\n## Changelog\n\n${changelog}\n\n## Notes\n\n- [ ] Please check if the version number is correct\n- [ ] Please confirm all tests have passed\n- [ ] Please confirm the documentation has been updated\n\n> This PR is auto created by release process, please contact the frontend team if there are any questions.",
22
- label: {
23
- color: "1A7F37",
24
- description: "Release PR",
25
- name: "CI-Release"
26
- },
27
- packagesDirectories: [],
28
- changePackagesLabel: "changes:${name}"
29
- },
15
+ // release: {
16
+ // publishPath: '',
17
+ // autoMergeReleasePR: false,
18
+ // autoMergeType: 'squash',
19
+ // branchName: 'release-${pkgName}-${tagName}',
20
+ // PRTitle:
21
+ // '[${pkgName} Release] Branch:${branch}, Tag:${tagName}, Env:${env}',
22
+ // PRBody:
23
+ // '## Publish Details\n\n- 🏷️ Version: ${tagName}\n- 🌲 Branch: ${branch}\n- 🔧 Environment: ${env}\n\n## Changelog\n\n${changelog}\n\n## Notes\n\n- [ ] Please check if the version number is correct\n- [ ] Please confirm all tests have passed\n- [ ] Please confirm the documentation has been updated\n\n> This PR is auto created by release process, please contact the frontend team if there are any questions.',
24
+ // label: {
25
+ // color: '1A7F37',
26
+ // description: 'Release PR',
27
+ // name: 'CI-Release'
28
+ // },
29
+ // packagesDirectories: [],
30
+ // changePackagesLabel: 'changes:${name}'
31
+ // },
30
32
  envOrder: [".env.local", ".env.production", ".env"]
31
33
  };
32
34
 
@@ -67,6 +69,7 @@ var ColorFormatter = class {
67
69
  }) {
68
70
  this.levelColors = levelColors;
69
71
  }
72
+ levelColors;
70
73
  /**
71
74
  * Format log event by applying color to the level and preserving original arguments
72
75
  *
@@ -478,11 +481,395 @@ var ConfigSearch = class {
478
481
  };
479
482
 
480
483
  // src/implement/ScriptContext.ts
481
- import { merge as merge3, get as get2 } from "lodash-es";
482
- import { ExecutorContextImpl } from "@qlover/fe-corekit";
484
+ import { merge as merge4, get as get2 } from "lodash-es";
485
+
486
+ // ../fe-corekit/src/aborter/Aborter.ts
487
+ import { isString } from "lodash-es";
488
+
489
+ // ../fe-corekit/src/executor/impl/ExecutorContextImpl.ts
490
+ var runtimesStorage = /* @__PURE__ */ new WeakMap();
491
+ var ExecutorContextImpl = class {
492
+ _parameters;
493
+ _error;
494
+ _returnValue;
495
+ /**
496
+ * Creates a new ExecutorContextImpl instance
497
+ *
498
+ * **Important**: Parameters are stored by reference, not cloned.
499
+ * If you need parameter isolation, clone them before passing to the constructor.
500
+ *
501
+ * @param parameters - The initial parameters for the context
502
+ *
503
+ * @example Without isolation (parameters will be modified)
504
+ * ```typescript
505
+ * const params = { value: 1 };
506
+ * const context = new ExecutorContextImpl(params);
507
+ * context.setParameters({ value: 2 });
508
+ * console.log(params.value); // 2 - original object is modified
509
+ * ```
510
+ *
511
+ * @example With isolation (clone parameters first)
512
+ * ```typescript
513
+ * const params = { value: 1 };
514
+ * const context = new ExecutorContextImpl({ ...params }); // shallow clone
515
+ * context.setParameters({ value: 2 });
516
+ * console.log(params.value); // 1 - original object is unchanged
517
+ * ```
518
+ */
519
+ constructor(parameters) {
520
+ this._parameters = parameters;
521
+ runtimesStorage.set(this, {
522
+ pluginName: "",
523
+ pluginIndex: void 0,
524
+ hookName: "",
525
+ returnValue: void 0,
526
+ returnBreakChain: false,
527
+ times: 0,
528
+ breakChain: false
529
+ });
530
+ }
531
+ /**
532
+ * Get the context parameters
533
+ *
534
+ * **Important**: Returns the parameters by reference.
535
+ * Modifications to the returned object will affect the context's internal state.
536
+ *
537
+ * @override
538
+ * @returns The parameters object (by reference)
539
+ */
540
+ get parameters() {
541
+ return this._parameters;
542
+ }
543
+ /**
544
+ * Get the error, if any
545
+ *
546
+ * @override
547
+ * @returns The error, if any
548
+ */
549
+ get error() {
550
+ return this._error;
551
+ }
552
+ /**
553
+ * Get the return value, if any
554
+ *
555
+ * @override
556
+ * @returns The return value, if any
557
+ */
558
+ get returnValue() {
559
+ return this._returnValue;
560
+ }
561
+ /**
562
+ * Get read-only snapshot of hooks runtime information
563
+ *
564
+ * Core concept:
565
+ * Provides safe read-only access to runtime tracking information.
566
+ * Returns a frozen shallow copy to prevent accidental modifications.
567
+ *
568
+ * Security features:
569
+ * - Stored in WeakMap, truly private (cannot access via ._hooksRuntimes)
570
+ * - Returns a new frozen object (not the internal reference)
571
+ * - Object is frozen to prevent modifications
572
+ * - Setter throws error to prevent assignment
573
+ *
574
+ * Why WeakMap?
575
+ * - TypeScript's `private` is only compile-time protection
576
+ * - JavaScript runtime can still access `._hooksRuntimes`
577
+ * - WeakMap provides true runtime privacy
578
+ * - No way to access the internal state from outside
579
+ *
580
+ * @override
581
+ * @returns Frozen shallow copy of hook runtime state
582
+ *
583
+ * @example Safe read access
584
+ * ```typescript
585
+ * const runtimes = context.hooksRuntimes;
586
+ * console.log(`Hook ${runtimes.hookName} executed ${runtimes.times} times`);
587
+ * ```
588
+ *
589
+ * @example Modification attempts fail
590
+ * ```typescript
591
+ * const runtimes = context.hooksRuntimes;
592
+ * runtimes.times = 10; // Error: Cannot assign to read only property
593
+ * context.hooksRuntimes = {}; // Error: hooksRuntimes is read-only
594
+ * context._hooksRuntimes; // undefined - truly private!
595
+ * ```
596
+ */
597
+ get hooksRuntimes() {
598
+ const runtimes = runtimesStorage.get(this);
599
+ if (!runtimes) {
600
+ throw new Error("Runtime state not initialized");
601
+ }
602
+ return Object.freeze({ ...runtimes });
603
+ }
604
+ /**
605
+ * Set error in context
606
+ *
607
+ * Automatically converts standard Error objects to ExecutorError for consistency.
608
+ * If the error is already an ExecutorError, it is stored as-is.
609
+ * If the error is a standard Error, it is wrapped in an ExecutorError with id 'EXECUTOR_ERROR'.
610
+ *
611
+ * @override
612
+ * @param error - The error to set in context (ExecutorError or standard Error)
613
+ *
614
+ * @example With ExecutorError
615
+ * ```typescript
616
+ * context.setError(new ExecutorError('VALIDATION_ERROR', 'Invalid input'));
617
+ * console.log(context.error.id); // 'VALIDATION_ERROR'
618
+ * ```
619
+ *
620
+ * @example With standard Error (auto-converted)
621
+ * ```typescript
622
+ * try {
623
+ * JSON.parse('invalid');
624
+ * } catch (error) {
625
+ * context.setError(error); // Auto-converted to ExecutorError
626
+ * console.log(context.error.id); // 'EXECUTOR_ERROR'
627
+ * console.log(context.error.cause); // Original SyntaxError
628
+ * }
629
+ * ```
630
+ */
631
+ setError(error) {
632
+ this._error = error;
633
+ }
634
+ /**
635
+ * Set return value in context
636
+ *
637
+ * @override
638
+ * @param value - The value to set as return value
639
+ */
640
+ setReturnValue(value) {
641
+ this._returnValue = value;
642
+ }
643
+ /**
644
+ * Set parameters in context
645
+ *
646
+ * **Important**: Parameters are stored by reference, not cloned.
647
+ * The provided parameters object will be used directly.
648
+ *
649
+ * @override
650
+ * @param params - The parameters to set (stored by reference)
651
+ *
652
+ * @example
653
+ * ```typescript
654
+ * const newParams = { value: 2 };
655
+ * context.setParameters(newParams);
656
+ * // context.parameters === newParams (same reference)
657
+ * ```
658
+ */
659
+ setParameters(params) {
660
+ this._parameters = params;
661
+ }
662
+ /**
663
+ * Reset hooks runtime state to initial values
664
+ *
665
+ * Core concept:
666
+ * Clears all runtime tracking information for fresh execution
667
+ *
668
+ * Reset operations:
669
+ * - Clears plugin name and hook name
670
+ * - Resets return value and chain breaking flags
671
+ * - Resets execution counter and index
672
+ *
673
+ * @override
674
+ */
675
+ resetHooksRuntimes(hookName) {
676
+ const runtimes = runtimesStorage.get(this);
677
+ if (!runtimes) {
678
+ throw new Error("Runtime state not initialized");
679
+ }
680
+ if (hookName) {
681
+ runtimesStorage.set(this, {
682
+ ...runtimes,
683
+ hookName,
684
+ times: 0,
685
+ returnValue: void 0
686
+ });
687
+ return;
688
+ }
689
+ runtimesStorage.set(this, {
690
+ pluginName: "",
691
+ pluginIndex: void 0,
692
+ hookName: "",
693
+ returnValue: void 0,
694
+ returnBreakChain: false,
695
+ times: 0,
696
+ breakChain: false
697
+ });
698
+ }
699
+ /**
700
+ * Reset entire context to initial state
701
+ *
702
+ * Core concept:
703
+ * Complete context cleanup for new execution cycle
704
+ *
705
+ * Reset operations:
706
+ * - Resets hooks runtime state
707
+ * - Clears return value
708
+ * - Clears error state
709
+ *
710
+ * @override
711
+ */
712
+ reset() {
713
+ this.resetHooksRuntimes();
714
+ this._returnValue = void 0;
715
+ this._error = void 0;
716
+ }
717
+ /**
718
+ * Check if a plugin hook should be skipped
719
+ * Returns true if the hook should be skipped (invalid or disabled)
720
+ *
721
+ * Core concept:
722
+ * Plugin hook validation and enablement checking
723
+ *
724
+ * Validation criteria:
725
+ * - Hook method exists and is callable
726
+ * - Plugin is enabled for the specific hook
727
+ * - Plugin enablement function returns true
728
+ *
729
+ * @override
730
+ * @template Ctx - Type of task context
731
+ * @param plugin - The plugin to check
732
+ * @param hookName - The name of the hook to validate
733
+ * @returns True if the hook should be skipped, false otherwise
734
+ */
735
+ shouldSkipPluginHook(plugin, hookName) {
736
+ return typeof plugin[hookName] !== "function" || typeof plugin.enabled === "function" && !plugin.enabled(hookName, this);
737
+ }
738
+ /**
739
+ * Update runtime tracking information for plugin execution
740
+ *
741
+ * Core concept:
742
+ * Track plugin execution metadata for debugging and flow control.
743
+ * Creates a new runtime object with merged updates to ensure immutability.
744
+ *
745
+ * Security:
746
+ * - Always creates a new object (immutable updates)
747
+ * - Stored in WeakMap (truly private)
748
+ * - Cannot be accessed or modified from outside
749
+ *
750
+ * Tracking information:
751
+ * - Current plugin name
752
+ * - Current hook name
753
+ * - Execution counter (times)
754
+ * - Plugin index in execution chain
755
+ *
756
+ * @override
757
+ * @param updates - Partial runtime updates to merge
758
+ * @example
759
+ * ```typescript
760
+ * context.runtimes({
761
+ * pluginName: 'testPlugin',
762
+ * hookName: 'onBefore',
763
+ * times: 1,
764
+ * pluginIndex: 0
765
+ * });
766
+ * ```
767
+ */
768
+ runtimes(updates) {
769
+ const current = runtimesStorage.get(this);
770
+ if (!current) {
771
+ throw new Error("Runtime state not initialized");
772
+ }
773
+ runtimesStorage.set(this, {
774
+ ...current,
775
+ ...updates
776
+ });
777
+ }
778
+ /**
779
+ * Set return value in context runtime tracking
780
+ *
781
+ * Core concept:
782
+ * Store plugin hook return value for chain control and debugging.
783
+ * Creates a new runtime object with updated return value.
784
+ *
785
+ * Security:
786
+ * - Creates new object (immutable update)
787
+ * - Stored in WeakMap (truly private)
788
+ *
789
+ * Usage scenarios:
790
+ * - Track plugin hook return values
791
+ * - Enable chain breaking based on return values
792
+ * - Debug plugin execution flow
793
+ *
794
+ * @override
795
+ * @param returnValue - The value to set as return value
796
+ */
797
+ runtimeReturnValue(returnValue) {
798
+ const current = runtimesStorage.get(this);
799
+ if (!current) {
800
+ throw new Error("Runtime state not initialized");
801
+ }
802
+ runtimesStorage.set(this, {
803
+ ...current,
804
+ returnValue
805
+ });
806
+ }
807
+ /**
808
+ * Check if the execution chain should be broken
809
+ *
810
+ * Core concept:
811
+ * Chain breaking control for plugin execution flow
812
+ *
813
+ * Chain breaking scenarios:
814
+ * - Plugin explicitly sets breakChain flag
815
+ * - Error conditions requiring immediate termination
816
+ * - Business logic requiring early exit
817
+ *
818
+ * @override
819
+ * @returns True if the chain should be broken, false otherwise
820
+ */
821
+ shouldBreakChain() {
822
+ const runtimes = runtimesStorage.get(this);
823
+ return !!runtimes?.breakChain;
824
+ }
825
+ /**
826
+ * Check if the execution chain should be broken due to return value
827
+ *
828
+ * Core concept:
829
+ * Return value-based chain breaking control
830
+ *
831
+ * Usage scenarios:
832
+ * - Plugin returns a value that should terminate execution
833
+ * - Error handling hooks return error objects
834
+ * - Business logic requires return value-based flow control
835
+ *
836
+ * @override
837
+ * @returns True if the chain should be broken due to return value, false otherwise
838
+ */
839
+ shouldBreakChainOnReturn() {
840
+ const runtimes = runtimesStorage.get(this);
841
+ return !!runtimes?.returnBreakChain;
842
+ }
843
+ /**
844
+ * Check if execution should continue on error
845
+ *
846
+ * Core concept:
847
+ * Determines whether to continue executing subsequent plugins when a plugin hook
848
+ * throws an error, enabling resilient execution pipelines
849
+ *
850
+ * @override
851
+ * @returns True if execution should continue on error, false otherwise
852
+ */
853
+ shouldContinueOnError() {
854
+ const runtimes = runtimesStorage.get(this);
855
+ return !!runtimes?.continueOnError;
856
+ }
857
+ };
858
+
859
+ // ../fe-corekit/src/request/adapter/RequestAdapterFetch.ts
860
+ import { merge, pick } from "lodash-es";
861
+
862
+ // ../fe-corekit/src/request/impl/RequestExecutor.ts
863
+ import { clone } from "lodash-es";
864
+
865
+ // ../fe-corekit/src/request/impl/RequestPlugin.ts
866
+ import { clone as clone2 } from "lodash-es";
867
+
868
+ // ../fe-corekit/src/request/impl/ResponsePlugin.ts
869
+ import { isFunction } from "lodash-es";
483
870
 
484
871
  // src/utils/initializeOptions.ts
485
- import { merge, get } from "lodash-es";
872
+ import { merge as merge2, get } from "lodash-es";
486
873
  import { Env } from "@qlover/env-loader";
487
874
  var DEFAULT_ENV_ORDER = [".env.local", ".env"];
488
875
  function getDefaultOptions(options, feConfig, logger) {
@@ -513,7 +900,7 @@ function getDefaultStore(scriptName, sources, logger) {
513
900
  }
514
901
  function initializeOptions(scriptName, feConfig, options, logger) {
515
902
  const store = getDefaultStore(scriptName, feConfig, logger);
516
- const mergedOptions = merge({}, store, options);
903
+ const mergedOptions = merge2({}, store, options);
517
904
  const defaultOptions = getDefaultOptions(
518
905
  mergedOptions,
519
906
  feConfig,
@@ -524,7 +911,7 @@ function initializeOptions(scriptName, feConfig, options, logger) {
524
911
 
525
912
  // src/utils/contextDefaults.ts
526
913
  import { exec } from "child_process";
527
- import { merge as merge2 } from "lodash-es";
914
+ import { merge as merge3 } from "lodash-es";
528
915
 
529
916
  // ../logger/src/interface/LogEvent.ts
530
917
  var LogEvent = class {
@@ -535,6 +922,10 @@ var LogEvent = class {
535
922
  this.context = context;
536
923
  this.timestamp = Date.now();
537
924
  }
925
+ level;
926
+ args;
927
+ loggerName;
928
+ context;
538
929
  /**
539
930
  * Timestamp when the log event was created
540
931
  *
@@ -650,6 +1041,7 @@ var LogContext = class {
650
1041
  constructor(value) {
651
1042
  this.value = value;
652
1043
  }
1044
+ value;
653
1045
  };
654
1046
 
655
1047
  // ../logger/src/Logger.ts
@@ -678,6 +1070,7 @@ var Logger = class {
678
1070
  options.levels = options.levels || defaultLevels;
679
1071
  options.handlers = Array.isArray(options.handlers) ? options.handlers : options.handlers ? [options.handlers] : [];
680
1072
  }
1073
+ options;
681
1074
  /**
682
1075
  * Adds a new log handler to the logger
683
1076
  *
@@ -994,6 +1387,7 @@ var ConsoleHandler = class {
994
1387
  constructor(formatter = null) {
995
1388
  this.formatter = formatter;
996
1389
  }
1390
+ formatter;
997
1391
  /**
998
1392
  * Sets or updates the formatter for this handler
999
1393
  *
@@ -1172,6 +1566,7 @@ var TimestampFormatter = class {
1172
1566
  constructor(options = {}) {
1173
1567
  this.options = options;
1174
1568
  }
1569
+ options;
1175
1570
  /**
1176
1571
  * Updates formatter options at runtime.
1177
1572
  * Merges the given partial options into the current options; subsequent formatting uses the updated values.
@@ -1298,8 +1693,270 @@ var TimestampFormatter = class {
1298
1693
  }
1299
1694
  };
1300
1695
 
1696
+ // src/implement/TemplateEngine.ts
1697
+ var interpolatePreset = {
1698
+ /**
1699
+ * ES6 template-literal style: `${ variable }`
1700
+ *
1701
+ * This is the default for {@link TemplateEngine}.
1702
+ *
1703
+ * @example
1704
+ * ```typescript
1705
+ * engine.render('${user.name}', { user: { name: 'Bob' } });
1706
+ * ```
1707
+ */
1708
+ ES6: /\$\{([\s\S]+?)\}/g,
1709
+ /**
1710
+ * Lodash / EJS style: `<%= variable %>`
1711
+ *
1712
+ * Useful when migrating existing lodash templates.
1713
+ *
1714
+ * @example
1715
+ * ```typescript
1716
+ * new TemplateEngine({ interpolate: interpolatePreset.LODASH })
1717
+ * .render('<%= repo.url %>', { repo: { url: 'https://example.com' } });
1718
+ * ```
1719
+ */
1720
+ LODASH: /<%=([\s\S]+?)%>/g,
1721
+ /**
1722
+ * Mustache / Handlebars style: `{{ variable }}`
1723
+ *
1724
+ * @example
1725
+ * ```typescript
1726
+ * new TemplateEngine({ interpolate: interpolatePreset.MUSTACHE })
1727
+ * .render('{{ user.name }}', { user: { name: 'Alice' } });
1728
+ * ```
1729
+ */
1730
+ MUSTACHE: /\{\{([\s\S]+?)\}\}/g
1731
+ };
1732
+ var DANGEROUS_KEYS = /* @__PURE__ */ new Set([
1733
+ "__proto__",
1734
+ "constructor",
1735
+ "prototype",
1736
+ "__defineGetter__",
1737
+ "__defineSetter__",
1738
+ "__lookupGetter__",
1739
+ "__lookupSetter__"
1740
+ ]);
1741
+ var SAFE_PATH = /^[\w.[\]]+$/;
1742
+ var PATH_SEGMENT = /^(\d+|\w+)$/;
1743
+ function cloneGlobalRegExp(regex) {
1744
+ const flags = regex.flags.includes("g") ? regex.flags : `${regex.flags}g`;
1745
+ return new RegExp(regex.source, flags);
1746
+ }
1747
+ function normalizePath(path) {
1748
+ return path.replace(/\[(\d+)\]/g, ".$1");
1749
+ }
1750
+ function parsePathKeys(path, variablePrefix) {
1751
+ const trimmed = path.trim();
1752
+ if (!trimmed || !SAFE_PATH.test(trimmed)) {
1753
+ return null;
1754
+ }
1755
+ let actualPath = trimmed;
1756
+ if (variablePrefix) {
1757
+ if (actualPath === variablePrefix) {
1758
+ return [];
1759
+ }
1760
+ const prefix = `${variablePrefix}.`;
1761
+ if (!actualPath.startsWith(prefix)) {
1762
+ return null;
1763
+ }
1764
+ actualPath = actualPath.slice(prefix.length);
1765
+ if (!actualPath) {
1766
+ return null;
1767
+ }
1768
+ }
1769
+ const keys = normalizePath(actualPath).split(".");
1770
+ if (keys.length === 0 || keys.some((key) => !key || !PATH_SEGMENT.test(key))) {
1771
+ return null;
1772
+ }
1773
+ return keys;
1774
+ }
1775
+ function escapeHtml(str) {
1776
+ return str.replace(/[&<>"']/g, (char) => {
1777
+ switch (char) {
1778
+ case "&":
1779
+ return "&amp;";
1780
+ case "<":
1781
+ return "&lt;";
1782
+ case ">":
1783
+ return "&gt;";
1784
+ case '"':
1785
+ return "&quot;";
1786
+ case "'":
1787
+ return "&#39;";
1788
+ default:
1789
+ return char;
1790
+ }
1791
+ });
1792
+ }
1793
+ function formatValue(value, stringifyObject) {
1794
+ if (value === void 0 || value === null) {
1795
+ return "";
1796
+ }
1797
+ if (typeof value === "object") {
1798
+ return stringifyObject ? JSON.stringify(value) : String(value);
1799
+ }
1800
+ return String(value);
1801
+ }
1802
+ function getValueByKeys(data, keys, safePrototype) {
1803
+ if (keys === null) {
1804
+ return void 0;
1805
+ }
1806
+ if (keys.length === 0) {
1807
+ return data;
1808
+ }
1809
+ let current = data;
1810
+ for (const key of keys) {
1811
+ if (safePrototype && DANGEROUS_KEYS.has(key)) {
1812
+ return void 0;
1813
+ }
1814
+ if (current === null || typeof current !== "object") {
1815
+ return void 0;
1816
+ }
1817
+ const record = current;
1818
+ if (safePrototype) {
1819
+ if (!Object.hasOwn(record, key)) {
1820
+ return void 0;
1821
+ }
1822
+ } else if (!(key in record)) {
1823
+ return void 0;
1824
+ }
1825
+ current = record[key];
1826
+ }
1827
+ return current;
1828
+ }
1829
+ function tokenize(template, interpolate) {
1830
+ const tokens = [];
1831
+ const regex = cloneGlobalRegExp(interpolate);
1832
+ let lastIndex = 0;
1833
+ let match;
1834
+ while ((match = regex.exec(template)) !== null) {
1835
+ if (match[0].length === 0) {
1836
+ regex.lastIndex += 1;
1837
+ continue;
1838
+ }
1839
+ if (match.index > lastIndex) {
1840
+ tokens.push(template.slice(lastIndex, match.index));
1841
+ }
1842
+ const rawPath = match[1]?.trim() ?? "";
1843
+ tokens.push({
1844
+ type: "variable",
1845
+ path: rawPath,
1846
+ raw: match[0],
1847
+ keys: null
1848
+ });
1849
+ lastIndex = regex.lastIndex;
1850
+ }
1851
+ if (lastIndex < template.length) {
1852
+ tokens.push(template.slice(lastIndex));
1853
+ }
1854
+ return tokens;
1855
+ }
1856
+ var TemplateEngine = class {
1857
+ options;
1858
+ /**
1859
+ * @param options - Engine configuration. All fields are optional;
1860
+ * defaults to ES6 `${}` interpolation with safe prototype access.
1861
+ */
1862
+ constructor(options = {}) {
1863
+ const defaults = {
1864
+ interpolate: interpolatePreset.ES6,
1865
+ variable: "",
1866
+ defaultValue: "",
1867
+ escapeHtml: false,
1868
+ stringifyObject: true,
1869
+ keepUnmatched: false,
1870
+ safePrototype: true
1871
+ };
1872
+ this.options = {
1873
+ ...defaults,
1874
+ ...options,
1875
+ interpolate: cloneGlobalRegExp(
1876
+ options.interpolate ?? defaults.interpolate
1877
+ )
1878
+ };
1879
+ }
1880
+ /**
1881
+ * Compile a template into a reusable render function.
1882
+ *
1883
+ * Path parsing and validation happen once at compile time; subsequent
1884
+ * renders only perform value lookup and string assembly.
1885
+ *
1886
+ * @param template - Template string containing placeholders
1887
+ * @returns Render function bound to the compiled template
1888
+ * @throws {TypeError} When template is not a string
1889
+ */
1890
+ compile(template) {
1891
+ if (typeof template !== "string") {
1892
+ throw new TypeError("Template must be a string");
1893
+ }
1894
+ const {
1895
+ variable,
1896
+ defaultValue,
1897
+ escapeHtml: shouldEscapeHtml,
1898
+ stringifyObject,
1899
+ keepUnmatched,
1900
+ safePrototype
1901
+ } = this.options;
1902
+ const tokens = tokenize(template, this.options.interpolate).map(
1903
+ (token) => {
1904
+ if (typeof token === "string") {
1905
+ return token;
1906
+ }
1907
+ return {
1908
+ ...token,
1909
+ keys: parsePathKeys(token.path, variable)
1910
+ };
1911
+ }
1912
+ );
1913
+ return (input) => {
1914
+ const data = input !== null && typeof input === "object" ? input : {};
1915
+ const parts = [];
1916
+ for (const token of tokens) {
1917
+ if (typeof token === "string") {
1918
+ parts.push(token);
1919
+ continue;
1920
+ }
1921
+ const value = getValueByKeys(data, token.keys, safePrototype);
1922
+ if (value === void 0 || value === null) {
1923
+ if (keepUnmatched) {
1924
+ parts.push(token.raw);
1925
+ continue;
1926
+ }
1927
+ let resolvedDefault = defaultValue;
1928
+ if (typeof defaultValue === "function") {
1929
+ resolvedDefault = defaultValue(token.path, data);
1930
+ }
1931
+ const defaultStr = formatValue(resolvedDefault, stringifyObject);
1932
+ parts.push(shouldEscapeHtml ? escapeHtml(defaultStr) : defaultStr);
1933
+ continue;
1934
+ }
1935
+ let output = formatValue(value, stringifyObject);
1936
+ if (shouldEscapeHtml) {
1937
+ output = escapeHtml(output);
1938
+ }
1939
+ parts.push(output);
1940
+ }
1941
+ return parts.join("");
1942
+ };
1943
+ }
1944
+ /**
1945
+ * Render a template in one shot.
1946
+ *
1947
+ * Convenience wrapper around `compile(template)(data)`.
1948
+ * Prefer {@link compile} when the same template is rendered multiple times.
1949
+ *
1950
+ * @param template - Template string containing placeholders
1951
+ * @param data - Context object for variable substitution
1952
+ */
1953
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1954
+ render(template, data) {
1955
+ return this.compile(template)(data);
1956
+ }
1957
+ };
1958
+
1301
1959
  // src/implement/Shell.ts
1302
- import { template as lodashTemplate } from "lodash-es";
1303
1960
  var Shell = class _Shell {
1304
1961
  /**
1305
1962
  * Creates a new Shell instance with the specified configuration
@@ -1348,6 +2005,9 @@ var Shell = class _Shell {
1348
2005
  this.config = config;
1349
2006
  this.cache = cache;
1350
2007
  }
2008
+ config;
2009
+ cache;
2010
+ static templateEngine = new TemplateEngine();
1351
2011
  /**
1352
2012
  * Gets the logger instance for command logging and error reporting
1353
2013
  *
@@ -1373,60 +2033,32 @@ var Shell = class _Shell {
1373
2033
  return this.config.logger;
1374
2034
  }
1375
2035
  /**
1376
- * Formats a template string with context using lodash template
2036
+ * Formats a template string with context using {@link TemplateEngine}.
1377
2037
  *
1378
- * Core concept:
1379
- * Provides static template formatting functionality using
1380
- * lodash template for string interpolation with context data.
1381
- *
1382
- * Template features:
1383
- * - Uses lodash template for powerful string interpolation
1384
- * - Supports complex template expressions and logic
1385
- * - Handles undefined and null context values gracefully
1386
- * - Returns formatted string with context values substituted
1387
- *
1388
- * Template syntax:
1389
- * - `<%= variable %>` for escaped output
1390
- * - `<%- variable %>` for unescaped output
1391
- * - `<% code %>` for JavaScript code execution
1392
- * - Supports nested object access and function calls
1393
- *
1394
- * Context handling:
1395
- * - Accepts any object with string/number/boolean values
1396
- * - Handles undefined and null values safely
1397
- * - Supports nested object property access
1398
- * - Provides fallback for missing context values
2038
+ * Uses ES6-style `${ path }` placeholders via {@link TemplateEngine}.
1399
2039
  *
1400
2040
  * @param template - Template string with interpolation placeholders
1401
2041
  * @param context - Context object for template variable substitution
1402
2042
  * @returns Formatted string with context values substituted
1403
2043
  *
1404
- * @example Basic template formatting
2044
+ * @example Basic formatting
1405
2045
  * ```typescript
1406
- * const result = Shell.format('Hello <%= name %>!', { name: 'World' });
2046
+ * const result = Shell.format('Hello ${name}!', { name: 'World' });
1407
2047
  * // Returns: 'Hello World!'
1408
2048
  * ```
1409
2049
  *
1410
2050
  * @example Complex template with nested objects
1411
2051
  * ```typescript
1412
2052
  * const result = Shell.format(
1413
- * 'git clone <%= repo.url %> <%= repo.branch %>',
2053
+ * 'git clone ${repo.url} ${repo.branch}',
1414
2054
  * { repo: { url: 'https://github.com/user/repo.git', branch: 'main' } }
1415
2055
  * );
1416
2056
  * // Returns: 'git clone https://github.com/user/repo.git main'
1417
2057
  * ```
1418
- *
1419
- * @example Template with conditional logic
1420
- * ```typescript
1421
- * const result = Shell.format(
1422
- * 'npm install<% if (dev) { %> --save-dev<% } %>',
1423
- * { dev: true }
1424
- * );
1425
- * // Returns: 'npm install --save-dev'
1426
- * ```
1427
2058
  */
1428
2059
  static format(template = "", context = {}) {
1429
- return lodashTemplate(template)(context);
2060
+ const data = context !== null && typeof context === "object" ? context : {};
2061
+ return _Shell.templateEngine.render(template, data);
1430
2062
  }
1431
2063
  /**
1432
2064
  * Formats a template string with context and comprehensive error handling
@@ -1461,14 +2093,14 @@ var Shell = class _Shell {
1461
2093
  *
1462
2094
  * @example Basic formatting
1463
2095
  * ```typescript
1464
- * const result = shell.format('Hello <%= name %>!', { name: 'World' });
2096
+ * const result = shell.format('Hello ${name}!', { name: 'World' });
1465
2097
  * // Returns: 'Hello World!'
1466
2098
  * ```
1467
2099
  *
1468
2100
  * @example Error handling
1469
2101
  * ```typescript
1470
2102
  * try {
1471
- * const result = shell.format('Hello <%= name %>!', {});
2103
+ * const result = shell.format('Hello ${name}!', {});
1472
2104
  * } catch (error) {
1473
2105
  * // Error is logged with template and context information
1474
2106
  * console.error('Template formatting failed:', error.message);
@@ -1522,7 +2154,7 @@ ${JSON.stringify(context)}`
1522
2154
  *
1523
2155
  * @example String command with template
1524
2156
  * ```typescript
1525
- * const output = await shell.exec('git clone <%= repo %>', {
2157
+ * const output = await shell.exec('git clone ${repo}', {
1526
2158
  * context: { repo: 'https://github.com/user/repo.git' }
1527
2159
  * });
1528
2160
  * ```
@@ -1795,7 +2427,7 @@ var contextDefaults = {
1795
2427
  getConfig(feConfig) {
1796
2428
  return new ConfigSearch({
1797
2429
  name: "fe-config",
1798
- defaultConfig: merge2({}, defaultFeConfig, feConfig)
2430
+ defaultConfig: merge3({}, defaultFeConfig, feConfig)
1799
2431
  });
1800
2432
  },
1801
2433
  /**
@@ -1914,7 +2546,7 @@ var contextDefaults = {
1914
2546
  dryRun,
1915
2547
  execPromise: _options.execPromise || contextDefaults.exec
1916
2548
  });
1917
- const _feConfig = merge2({}, feConfig, { [name]: ctxOpts.options });
2549
+ const _feConfig = merge3({}, feConfig, { [name]: ctxOpts.options });
1918
2550
  return {
1919
2551
  options: _options,
1920
2552
  dryRun: !!dryRun,
@@ -1986,8 +2618,9 @@ var ScriptContext = class extends ExecutorContextImpl {
1986
2618
  this.shell = shell;
1987
2619
  this.dryRun = dryRun;
1988
2620
  this.verbose = verbose;
1989
- this.setOptions(initializeOptions(name, feConfig, options, logger));
2621
+ this.setParameters(initializeOptions(name, feConfig, options, logger));
1990
2622
  }
2623
+ name;
1991
2624
  /**
1992
2625
  * Logger instance for structured logging
1993
2626
  *
@@ -2024,7 +2657,22 @@ var ScriptContext = class extends ExecutorContextImpl {
2024
2657
  */
2025
2658
  verbose;
2026
2659
  /**
2660
+ * 该属性只是一个 parameters 的别名
2661
+ *
2662
+ * 当你需要设置最新的参数时可以直接使用 setParameters 方法
2663
+ *
2027
2664
  * @override
2665
+ * @example
2666
+ * ```ts
2667
+ * context.setParameters({
2668
+ * target: 'production'
2669
+ * });
2670
+ *
2671
+ * console.log(context.options);
2672
+ * // { target: 'production' }
2673
+ * console.log(context.parameters);
2674
+ * // { target: 'production' }
2675
+ * ```
2028
2676
  */
2029
2677
  get options() {
2030
2678
  return this.parameters;
@@ -2071,10 +2719,10 @@ var ScriptContext = class extends ExecutorContextImpl {
2071
2719
  * ```
2072
2720
  */
2073
2721
  get env() {
2074
- if (!this.options.env) {
2722
+ if (!this.parameters.env) {
2075
2723
  throw new Error("Environment is not initialized");
2076
2724
  }
2077
- return this.options.env;
2725
+ return this.parameters.env;
2078
2726
  }
2079
2727
  /**
2080
2728
  * Updates script options with deep merging support
@@ -2119,9 +2767,10 @@ var ScriptContext = class extends ExecutorContextImpl {
2119
2767
  * });
2120
2768
  * // Merges nested build configuration
2121
2769
  * ```
2770
+ *
2122
2771
  */
2123
- setOptions(options) {
2124
- this.setParameters(merge3(this.options, options));
2772
+ setParameters(params) {
2773
+ super.setParameters(merge4(this.parameters, params));
2125
2774
  }
2126
2775
  /**
2127
2776
  * Retrieves environment variable with optional default value
@@ -2224,16 +2873,16 @@ var ScriptContext = class extends ExecutorContextImpl {
2224
2873
  * // Returns the complete options object
2225
2874
  * ```
2226
2875
  */
2227
- getOptions(key, defaultValue) {
2876
+ getParameters(key, defaultValue) {
2228
2877
  if (!key) {
2229
- return this.options;
2878
+ return this.parameters;
2230
2879
  }
2231
- return get2(this.options, key, defaultValue);
2880
+ return get2(this.parameters, key, defaultValue);
2232
2881
  }
2233
2882
  };
2234
2883
 
2235
2884
  // src/implement/ScriptPlugin.ts
2236
- import { merge as merge4 } from "lodash-es";
2885
+ import { merge as merge5 } from "lodash-es";
2237
2886
  var ScriptPlugin = class {
2238
2887
  /**
2239
2888
  * Creates a new script plugin instance
@@ -2265,6 +2914,9 @@ var ScriptPlugin = class {
2265
2914
  this.props = props;
2266
2915
  this.setConfig(this.getInitialProps(props));
2267
2916
  }
2917
+ context;
2918
+ pluginName;
2919
+ props;
2268
2920
  /** Ensures only one instance of this plugin can be registered */
2269
2921
  onlyOne = true;
2270
2922
  /**
@@ -2292,9 +2944,9 @@ var ScriptPlugin = class {
2292
2944
  return {};
2293
2945
  }
2294
2946
  const pluginConfig = this.context.options[this.pluginName];
2295
- const fileConfig = this.context.getOptions(this.pluginName);
2947
+ const fileConfig = this.context.getParameters(this.pluginName);
2296
2948
  const baseConfig = pluginConfig || fileConfig;
2297
- return props ? merge4({}, baseConfig, props) : baseConfig || {};
2949
+ return props ? merge5({}, baseConfig, props) : baseConfig || {};
2298
2950
  }
2299
2951
  /**
2300
2952
  * Logger instance for structured logging
@@ -2357,8 +3009,8 @@ var ScriptPlugin = class {
2357
3009
  * }
2358
3010
  * ```
2359
3011
  */
2360
- get options() {
2361
- return this.context.getOptions(this.pluginName, {});
3012
+ get config() {
3013
+ return this.context.getParameters(this.pluginName, {});
2362
3014
  }
2363
3015
  /**
2364
3016
  * Determines whether a lifecycle method should be executed
@@ -2385,12 +3037,18 @@ var ScriptPlugin = class {
2385
3037
  * plugin.enabled('onExec', context); // Returns true
2386
3038
  * ```
2387
3039
  */
2388
- enabled(_name, _context) {
3040
+ enabled(name, _context) {
2389
3041
  const skip = this.getConfig("skip");
2390
- if (skip === true) {
3042
+ if (typeof skip === "string" && name === skip) {
3043
+ this.logger.debug(
3044
+ `Skip ${this.pluginName}.${name}, ${this.pluginName}.skip is set to '${skip}'`
3045
+ );
2391
3046
  return false;
2392
3047
  }
2393
- if (typeof skip === "string" && _name === skip) {
3048
+ if (skip === true) {
3049
+ this.logger.debug(
3050
+ `Skip ${this.pluginName}.${name}, ${this.pluginName}.skip is set to true`
3051
+ );
2394
3052
  return false;
2395
3053
  }
2396
3054
  return true;
@@ -2428,9 +3086,9 @@ var ScriptPlugin = class {
2428
3086
  */
2429
3087
  getConfig(keys, defaultValue) {
2430
3088
  if (!keys) {
2431
- return this.context.getOptions(this.pluginName, defaultValue);
3089
+ return this.context.getParameters(this.pluginName, defaultValue);
2432
3090
  }
2433
- return this.context.getOptions(
3091
+ return this.context.getParameters(
2434
3092
  [this.pluginName, ...Array.isArray(keys) ? keys : [keys]],
2435
3093
  defaultValue
2436
3094
  );
@@ -2467,167 +3125,10 @@ var ScriptPlugin = class {
2467
3125
  * ```
2468
3126
  */
2469
3127
  setConfig(config) {
2470
- this.context.setOptions({
3128
+ this.context.setParameters({
2471
3129
  [this.pluginName]: config
2472
3130
  });
2473
3131
  }
2474
- /**
2475
- * Lifecycle method called before script execution
2476
- *
2477
- * Override this method to perform setup tasks such as:
2478
- * - Environment validation
2479
- * - Configuration verification
2480
- * - Resource preparation
2481
- * - Pre-execution checks
2482
- *
2483
- * @override
2484
- * @param _context - Executor context containing execution state
2485
- *
2486
- * @example
2487
- * ```typescript
2488
- * async onBefore(context: ExecutorContext<MyContext>): Promise<void> {
2489
- * // Validate required environment variables
2490
- * const apiKey = this.context.getEnv('API_KEY');
2491
- * if (!apiKey) {
2492
- * throw new Error('API_KEY environment variable is required');
2493
- * }
2494
- *
2495
- * // Check if output directory exists
2496
- * const outputDir = this.getConfig('outputDir', './dist');
2497
- * if (!(await this.shell.exists(outputDir))) {
2498
- * await this.shell.mkdir(outputDir);
2499
- * }
2500
- * }
2501
- * ```
2502
- */
2503
- onBefore(_context) {
2504
- return void 0;
2505
- }
2506
- /**
2507
- * Lifecycle method called during script execution
2508
- *
2509
- * Override this method to implement the main plugin logic:
2510
- * - Core functionality execution
2511
- * - Business logic implementation
2512
- * - Task orchestration
2513
- * - Process management
2514
- *
2515
- * @override
2516
- * @param _context - Executor context containing execution state
2517
- *
2518
- * @example
2519
- * ```typescript
2520
- * async onExec(context: Context): Promise<void> {
2521
- * await this.step({
2522
- * label: 'Building project',
2523
- * task: async () => {
2524
- * await this.shell.exec('npm run build');
2525
- * return 'build completed';
2526
- * }
2527
- * });
2528
- *
2529
- * await this.step({
2530
- * label: 'Running tests',
2531
- * task: async () => {
2532
- * await this.shell.exec('npm test');
2533
- * return 'tests passed';
2534
- * }
2535
- * });
2536
- * }
2537
- * ```
2538
- */
2539
- onExec(_context) {
2540
- }
2541
- /**
2542
- * Lifecycle method called after successful script execution
2543
- *
2544
- * Override this method to perform cleanup tasks such as:
2545
- * - Resource cleanup
2546
- * - Success notifications
2547
- * - Result processing
2548
- * - Post-execution reporting
2549
- *
2550
- * @override
2551
- * @param _context - Executor context containing execution state
2552
- *
2553
- * @example
2554
- * ```typescript
2555
- * async onSuccess(context: Context): Promise<void> {
2556
- * // Send success notification
2557
- * await this.sendNotification('Build completed successfully');
2558
- *
2559
- * // Generate success report
2560
- * await this.generateReport({
2561
- * status: 'success',
2562
- * timestamp: new Date(),
2563
- * duration: context.duration
2564
- * });
2565
- *
2566
- * // Clean up temporary files
2567
- * await this.shell.rmdir('./temp');
2568
- * }
2569
- * ```
2570
- */
2571
- onSuccess(_context) {
2572
- return void 0;
2573
- }
2574
- /**
2575
- * Lifecycle method called when script execution fails
2576
- *
2577
- * Override this method to handle errors such as:
2578
- * - Error logging and reporting
2579
- * - Resource cleanup on failure
2580
- * - Error notifications
2581
- * - Failure recovery attempts
2582
- *
2583
- * @override
2584
- * @param _context - Executor context containing execution state
2585
- *
2586
- * @example
2587
- * ```typescript
2588
- * async onError(context: Context): Promise<void> {
2589
- * // Log detailed error information
2590
- * this.logger.error('Script execution failed', {
2591
- * error: context.error,
2592
- * duration: context.duration,
2593
- * config: this.options
2594
- * });
2595
- *
2596
- * // Send error notification
2597
- * await this.sendNotification('Build failed', {
2598
- * error: context.error.message
2599
- * });
2600
- *
2601
- * // Clean up partial results
2602
- * await this.shell.rmdir('./partial-build');
2603
- * }
2604
- * ```
2605
- */
2606
- onError(_context) {
2607
- }
2608
- /**
2609
- * Lifecycle method called after script execution
2610
- *
2611
- * Override this method to perform cleanup tasks such as:
2612
- * - Resource cleanup
2613
- * - Success notifications
2614
- * - Result processing
2615
- * - Post-execution reporting
2616
- *
2617
- * @override
2618
- * @param _context - Executor context containing execution state
2619
- *
2620
- * @example
2621
- * ```typescript
2622
- * async onFinally(context: Context): Promise<void> {
2623
- * // Clean up temporary files
2624
- * await this.shell.rmdir('./temp');
2625
- * }
2626
- * ```
2627
- */
2628
- onFinally(_context) {
2629
- return void 0;
2630
- }
2631
3132
  /**
2632
3133
  * Executes a step with structured logging and error handling
2633
3134
  *
@@ -2688,18 +3189,25 @@ var ScriptPlugin = class {
2688
3189
  * ```
2689
3190
  */
2690
3191
  async step(options) {
2691
- this.logger.log();
2692
- this.logger.info(options.label);
2693
- this.logger.log();
3192
+ const stepBase = this.createStepLabel(options.label);
3193
+ this.logger.info(`${stepBase} - beginning`);
2694
3194
  try {
2695
3195
  const res = await options.task();
2696
- this.logger.info(`${options.label} - success`);
3196
+ this.logger.info(`${stepBase} - success`);
2697
3197
  return res;
2698
3198
  } catch (e) {
2699
- this.logger.error(e);
3199
+ this.logger.error(`${stepBase} - failed
3200
+ `, e);
2700
3201
  throw e;
2701
3202
  }
2702
3203
  }
3204
+ /**
3205
+ * Builds the shared step log prefix (plugin context + dry-run)
3206
+ */
3207
+ createStepLabel(label) {
3208
+ const stepLabel = `${this.pluginName}: ${label}`;
3209
+ return this.context.dryRun ? `[dry-run] ${stepLabel}` : stepLabel;
3210
+ }
2703
3211
  };
2704
3212
  export {
2705
3213
  ColorFormatter,
@@ -2707,5 +3215,7 @@ export {
2707
3215
  ScriptContext,
2708
3216
  ScriptPlugin,
2709
3217
  Shell,
2710
- defaultFeConfig
3218
+ TemplateEngine,
3219
+ defaultFeConfig,
3220
+ interpolatePreset
2711
3221
  };