generator-easy-ui5 3.9.1 → 3.10.0

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
@@ -6,6 +6,18 @@
6
6
  [![License Status][license-image]][license-url]
7
7
  [![REUSE status][reuse-image]][reuse-url]
8
8
 
9
+ <!-- prettier-ignore -->
10
+ ```text
11
+
12
+ ███████╗ █████╗ ███████╗██╗ ██╗ ██╗ ██╗██╗███████╗ ▄▄ ▄▄
13
+ ██╔════╝██╔══██╗██╔════╝╚██╗ ██╔╝ ██║ ██║██║██╔════╝ █████
14
+ █████╗ ███████║███████╗ ╚████╔╝ ██║ ██║██║███████╗ ███
15
+ ██╔══╝ ██╔══██║╚════██║ ╚██╔╝ ██║ ██║██║╚════██║ █
16
+ ███████╗██║ ██║███████║ ██║ ╚██████╔╝██║███████║
17
+ ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝╚══════╝ v3.10.0
18
+
19
+ ```
20
+
9
21
  ## Description
10
22
 
11
23
  Easy UI5 (`easy-ui5`) 💙 is a [Yeoman](http://yeoman.io/) generator which enables you to create simple [SAPUI5](https://ui5.sap.com)/[OpenUI5](https://sdk.openui5.org)-based web-apps and other UI5-related projects within seconds.
@@ -20,7 +32,7 @@ The purpose of the (preinstalled) `project generator` is to guide you on your fi
20
32
 
21
33
  ## Requirements
22
34
 
23
- - Get [Node.js](https://nodejs.org/en/download/) (:warning: **version 20 or higher**).
35
+ - Get [Node.js](https://nodejs.org/en/download/) (:warning: **version 20.19 or higher**).
24
36
 
25
37
  ## Download and Installation
26
38
 
@@ -105,6 +117,57 @@ Once you decided on the generator, run:
105
117
  yo easy-ui5 [project|ts-app|...]
106
118
  ```
107
119
 
120
+ ## Configuration
121
+
122
+ easy-ui5 reads its configuration from `~/.easyui5rc.json` (recommended) or
123
+ `~/.easyui5rc` (INI). Both files are optional and use bare option names — no
124
+ prefix needed.
125
+
126
+ `~/.easyui5rc.json`:
127
+
128
+ ```json
129
+ {
130
+ "ghAuthToken": "github_pat_...",
131
+ "ghBaseUrl": "https://github.tools.sap/api/v3",
132
+ "pluginsHome": "/Users/me/.npm/_generator-easy-ui5/plugin-generators",
133
+ "addGhBaseUrl": "https://github.tools.sap/api/v3",
134
+ "addGhOrg": "my-org",
135
+ "addSubGeneratorPrefix": "generator-"
136
+ }
137
+ ```
138
+
139
+ `~/.easyui5rc` (INI alternative):
140
+
141
+ ```text
142
+ ghAuthToken=github_pat_...
143
+ ghBaseUrl=https://github.tools.sap/api/v3
144
+ ```
145
+
146
+ ### Cascade & per-project overrides
147
+
148
+ Resolution walks from the current working directory up to your home directory
149
+ (ESLint-style). At each level, `.easyui5rc.json` wins over `.easyui5rc`; closer
150
+ files win over farther ones. Add `"root": true` to a file to stop the cascade
151
+ at that level.
152
+
153
+ ### Environment variables
154
+
155
+ Every option also accepts a `EASY_UI5_<SCREAMING_SNAKE>` environment variable
156
+ which takes precedence over every config file — ideal for CI:
157
+
158
+ ```sh
159
+ EASY_UI5_GH_AUTH_TOKEN=ghp_...
160
+ EASY_UI5_GH_BASE_URL=https://github.tools.sap/api/v3
161
+ EASY_UI5_PLUGINS_HOME=/var/cache/easy-ui5
162
+ ```
163
+
164
+ ### Legacy `~/.npmrc` keys (deprecated)
165
+
166
+ Earlier versions read `easy-ui5_*` keys directly from `~/.npmrc`. These are
167
+ still resolved for one release, but npm 11+ warns and npm 12 will reject
168
+ unknown user config — please move them to `~/.easyui5rc.json`. The generator
169
+ prints a one-time migration notice when it detects legacy keys.
170
+
108
171
  ## Proxy settings
109
172
 
110
173
  If you are running easy-ui5 behind a coporate proxy, just use the default proxy environment variables for Node.js to configure your corporate proxy:
@@ -135,6 +198,17 @@ Proxies can be passed as env variables or as npm config options. The highest pre
135
198
 
136
199
  Please use the GitHub bug tracking system to post questions, bug reports or to create pull requests.
137
200
 
201
+ ## Releases
202
+
203
+ Releases are automated via [Changesets](https://github.com/changesets/changesets) and the
204
+ [`Release` workflow](.github/workflows/release.yml). Contributors record the intended semver
205
+ bump and summary in a `.changeset/*.md` file as part of their PR (`npm run changeset`); when
206
+ that PR merges to `main`, a "Version Packages" PR is opened or updated. Merging that PR cuts
207
+ the actual release: version bump, `CHANGELOG.md` update, npm publish via OIDC Trusted
208
+ Publishing, git tag, GitHub Release.
209
+
210
+ See [CONTRIBUTING.md](CONTRIBUTING.md#release-life-cycle) for the contributor-facing details.
211
+
138
212
  ## Contributing
139
213
 
140
214
  We welcome any type of contribution (code contributions, pull requests, issues) to this project equally.
@@ -0,0 +1,92 @@
1
+ // Renders the easy-ui5 welcome banner.
2
+ //
3
+ // A box-drawing wordmark in the ANSI-Shadow style spelling out
4
+ // `EASY UI5`, rendered in a six-row rainbow when a chalk-style colorizer
5
+ // is supplied. A small heart in the same block style sits on the top
6
+ // right; an empty row separates the heart from the version which is
7
+ // printed on the wordmark's last row. The banner is bracketed by blank
8
+ // lines so it has visual breathing room when log output streams above
9
+ // and below it.
10
+
11
+ const WORDMARK = [
12
+ "███████╗ █████╗ ███████╗██╗ ██╗ ██╗ ██╗██╗███████╗",
13
+ "██╔════╝██╔══██╗██╔════╝╚██╗ ██╔╝ ██║ ██║██║██╔════╝",
14
+ "█████╗ ███████║███████╗ ╚████╔╝ ██║ ██║██║███████╗",
15
+ "██╔══╝ ██╔══██║╚════██║ ╚██╔╝ ██║ ██║██║╚════██║",
16
+ "███████╗██║ ██║███████║ ██║ ╚██████╔╝██║███████║",
17
+ "╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝╚══════╝",
18
+ ];
19
+
20
+ // One chalk colour per wordmark row — six rows, six rainbow tones.
21
+ const RAINBOW = ["red", "yellow", "green", "cyan", "blue", "magenta"];
22
+
23
+ // Small 4-row heart, anchored to the right of the wordmark's top half.
24
+ // Aligned manually so it visually rests against the wordmark with a small
25
+ // gap. Width includes leading spaces so each row has identical length.
26
+ const HEART = [" ▄▄ ▄▄ ", " █████ ", " ███ ", " █ "];
27
+
28
+ const WORDMARK_WIDTH = 56;
29
+ const HEART_GAP = 2; // spaces between the wordmark and the heart
30
+ const HEART_WIDTH = HEART[0].length;
31
+ const BANNER_WIDTH = WORDMARK_WIDTH + HEART_GAP + HEART_WIDTH;
32
+ const INDENT = " ";
33
+ // The version sits on the last wordmark row, leaving the row between the
34
+ // heart and the version blank for a bit of breathing space.
35
+ const VERSION_ROW_INDEX = WORDMARK.length - 1;
36
+
37
+ /**
38
+ * @param {string} version The generator version to render under the heart.
39
+ * @param {Partial<Record<"red"|"yellow"|"green"|"cyan"|"blue"|"magenta", (s:string)=>string>>} [c]
40
+ * Optional chalk-like colorizer. When omitted the banner renders
41
+ * without ANSI escapes.
42
+ * @returns {string}
43
+ */
44
+ export function renderBanner(version, c) {
45
+ const id = (s) => s;
46
+ const rainbow = RAINBOW.map((name) => c?.[name] ?? id);
47
+ const yellow = c?.yellow ?? id;
48
+ // The heart accent uses magenta as a pink fallback against any of the
49
+ // rainbow rows it overlaps. Fall back to red if the caller didn't
50
+ // supply magenta.
51
+ const pink = c?.magenta ?? c?.red ?? id;
52
+
53
+ const versionText = `v${version}`;
54
+ // Centre the version under the heart by padding it to the heart's width.
55
+ const versionRow = centerInWidth(versionText, HEART_WIDTH);
56
+
57
+ const lines = WORDMARK.map((line, i) => {
58
+ const colorize = rainbow[i] ?? id;
59
+ const heart = HEART[i];
60
+ const wordmark = `${INDENT}${colorize(line)}`;
61
+ if (heart) {
62
+ return `${wordmark}${" ".repeat(HEART_GAP)}${pink(heart)}`;
63
+ }
64
+ if (i === VERSION_ROW_INDEX) {
65
+ return `${wordmark}${" ".repeat(HEART_GAP)}${yellow(versionRow)}`;
66
+ }
67
+ return wordmark;
68
+ });
69
+
70
+ // Bracket the banner with blank lines so log output above and below has
71
+ // some breathing space.
72
+ return ["", ...lines, ""].join("\n");
73
+ }
74
+
75
+ function centerInWidth(text, width) {
76
+ if (text.length >= width) return text;
77
+ const pad = width - text.length;
78
+ const left = Math.floor(pad / 2);
79
+ const right = pad - left;
80
+ return `${" ".repeat(left)}${text}${" ".repeat(right)}`;
81
+ }
82
+
83
+ // Exported for tests that need the design constants without running render.
84
+ export const _internals = {
85
+ WORDMARK_WIDTH,
86
+ WORDMARK_HEIGHT: WORDMARK.length,
87
+ HEART_WIDTH,
88
+ HEART_HEIGHT: HEART.length,
89
+ BANNER_WIDTH,
90
+ RAINBOW,
91
+ VERSION_ROW_INDEX,
92
+ };
@@ -1,12 +1,278 @@
1
- import libnpmconfig from "libnpmconfig";
1
+ import { readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import path from "node:path";
2
4
 
3
- // helper to retrieve config entries from npm
4
- // --> npm config set easy-ui5_addGhOrg XYZ
5
+ import { createLogger } from "./log.js";
6
+
7
+ // Singleton cache of the merged config map.
5
8
  let npmConfig;
6
9
 
10
+ // Internal: keys we accept from .npmrc. Application keys (easy-ui5_*) are
11
+ // being phased out of .npmrc — npm 11+ warns and npm 12 will reject them.
12
+ // Proxy keys remain legitimate npm config and continue to be read from .npmrc.
13
+ const NPMRC_PROXY_KEYS = new Set(["http-proxy", "https-proxy", "proxy", "no-proxy"]);
14
+
15
+ // Map an easy-ui5 option name (`ghAuthToken`) to its EASY_UI5_* env-var name
16
+ // (`EASY_UI5_GH_AUTH_TOKEN`). Inserts an underscore before each upper-case run.
17
+ function envVarNameFor(optionName) {
18
+ const snake = optionName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2");
19
+ return `EASY_UI5_${snake.toUpperCase()}`;
20
+ }
21
+
22
+ // ---------- parsers ----------
23
+
24
+ // Minimal INI parser for .easyui5rc / .npmrc. Handles blank lines, `;`/`#`
25
+ // comments, and flat `key = value` pairs. No sections, no JSON coercion.
26
+ function parseIni(raw) {
27
+ const out = {};
28
+ for (const rawLine of raw.split(/\r?\n/)) {
29
+ let line = rawLine.replace(/(^|\s)[;#].*$/, "$1").trim();
30
+ if (!line) continue;
31
+ const eq = line.indexOf("=");
32
+ if (eq < 0) continue;
33
+ const key = line.slice(0, eq).trim();
34
+ let value = line.slice(eq + 1).trim();
35
+ if (!key) continue;
36
+ if ((value.startsWith('"') && value.endsWith('"') && value.length >= 2) || (value.startsWith("'") && value.endsWith("'") && value.length >= 2)) {
37
+ value = value.slice(1, -1);
38
+ }
39
+ out[key] = value;
40
+ }
41
+ return out;
42
+ }
43
+
44
+ function parseJson(raw) {
45
+ try {
46
+ const parsed = JSON.parse(raw);
47
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
48
+ } catch {
49
+ /* fall through */
50
+ }
51
+ return {};
52
+ }
53
+
54
+ function readFile(file) {
55
+ if (!file) return null;
56
+ try {
57
+ return readFileSync(file, "utf-8");
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ // Read a .npmrc, returning {data, raw}. We need the raw object to detect
64
+ // legacy easy-ui5_* keys for the deprecation warning even though we don't
65
+ // expose them as resolved config (we strip them to proxy-keys only).
66
+ function readNpmrc(file) {
67
+ const raw = readFile(file);
68
+ if (raw == null) return { all: {}, proxy: {} };
69
+ const all = parseIni(raw);
70
+ // also normalise `//easy-ui5_*` to the bare key, for legacy-detection only
71
+ for (const [k, v] of Object.entries(all)) {
72
+ if (k.startsWith("//easy-ui5_")) {
73
+ const stripped = k.slice(2);
74
+ if (!(stripped in all)) all[stripped] = v;
75
+ }
76
+ }
77
+ const proxy = {};
78
+ for (const key of NPMRC_PROXY_KEYS) {
79
+ if (key in all) proxy[key] = all[key];
80
+ }
81
+ return { all, proxy };
82
+ }
83
+
84
+ // Read an .easyui5rc[.json] file. JSON takes precedence over INI at the same
85
+ // directory level. Returns { data, root } where `root` indicates the file
86
+ // declared itself as the cascade root.
87
+ function readEasyUi5Rc(dir) {
88
+ const jsonPath = path.join(dir, ".easyui5rc.json");
89
+ const iniPath = path.join(dir, ".easyui5rc");
90
+
91
+ let data = {};
92
+ const jsonRaw = readFile(jsonPath);
93
+ if (jsonRaw != null) data = parseJson(jsonRaw);
94
+ else {
95
+ const iniRaw = readFile(iniPath);
96
+ if (iniRaw != null) data = parseIni(iniRaw);
97
+ }
98
+
99
+ const root = data && (data.root === true || data.root === "true");
100
+ if (root) delete data.root;
101
+ return { data, root };
102
+ }
103
+
104
+ // ---------- value transforms ----------
105
+
106
+ // npm-style ${ENV_VAR} interpolation in values, with a recursion guard.
107
+ function expandEnv(value, depth = 0) {
108
+ if (typeof value !== "string" || depth > 10) return value;
109
+ let replaced = false;
110
+ const out = value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (m, name) => {
111
+ const v = process.env[name];
112
+ if (v === undefined) return m;
113
+ replaced = true;
114
+ return v;
115
+ });
116
+ return replaced ? expandEnv(out, depth + 1) : out;
117
+ }
118
+
119
+ // ---------- env sources ----------
120
+
121
+ // Pull `npm_config_*` env vars; only proxy keys are honoured, because
122
+ // `easy-ui5_*` is no longer an npm-namespace concern.
123
+ function readEnvNpmConfig() {
124
+ const out = {};
125
+ for (const [k, v] of Object.entries(process.env)) {
126
+ if (!k.toLowerCase().startsWith("npm_config_")) continue;
127
+ const raw = k.slice("npm_config_".length);
128
+ const lower = raw.toLowerCase();
129
+ const dashed = lower.replace(/_/g, "-");
130
+ if (NPMRC_PROXY_KEYS.has(lower)) out[lower] = v;
131
+ if (NPMRC_PROXY_KEYS.has(dashed)) out[dashed] = v;
132
+ }
133
+ return out;
134
+ }
135
+
136
+ // Resolve `EASY_UI5_*` env vars to easy-ui5_<camelCase> keys. We only know
137
+ // the canonical option names when getNPMConfig() is called, so we don't
138
+ // pre-compute this map — instead `getNPMConfig` checks process.env directly
139
+ // after the file cascade misses.
140
+ //
141
+ // This function is unused for file-cascade building; kept here as a stub
142
+ // for symmetry / documentation. The real resolution happens in getNPMConfig.
143
+
144
+ // ---------- the cascade ----------
145
+
146
+ // Walk from `startDir` upward, collecting .easyui5rc[.json] at each level.
147
+ // Stops at a directory whose file sets `root: true`, or at homedir, or at
148
+ // filesystem root, whichever comes first. Always also reads ~/.easyui5rc at
149
+ // the end (lowest precedence) so a per-user file always applies.
150
+ //
151
+ // Result is ordered low-precedence-first.
152
+ function collectEasyUi5RcCascade(startDir) {
153
+ const home = homedir();
154
+ const visited = new Set();
155
+ const chain = []; // each entry: { dir, data }
156
+
157
+ let dir = path.resolve(startDir);
158
+ let stoppedAtRoot = false;
159
+ for (let i = 0; i < 64; i++) {
160
+ if (visited.has(dir)) break;
161
+ visited.add(dir);
162
+
163
+ const { data, root } = readEasyUi5Rc(dir);
164
+ if (Object.keys(data).length > 0 || root) {
165
+ chain.push({ dir, data });
166
+ }
167
+ if (root) {
168
+ stoppedAtRoot = true;
169
+ break;
170
+ }
171
+ if (dir === home) break;
172
+ const parent = path.dirname(dir);
173
+ if (parent === dir) break;
174
+ dir = parent;
175
+ }
176
+
177
+ // Always include the home file (if not already visited and not stopped by `root:true`)
178
+ if (!stoppedAtRoot && !visited.has(home)) {
179
+ const { data } = readEasyUi5Rc(home);
180
+ if (Object.keys(data).length > 0) chain.push({ dir: home, data });
181
+ }
182
+
183
+ // chain is ordered cwd → root (high precedence → low precedence).
184
+ // Reverse so callers can spread left-to-right with the usual
185
+ // "later wins" semantics.
186
+ return chain.reverse();
187
+ }
188
+
189
+ // ---------- legacy ~/.npmrc deprecation warning ----------
190
+
191
+ let legacyWarningEmitted = false;
192
+ function maybeWarnLegacyNpmrcKeys(npmrcAll) {
193
+ if (legacyWarningEmitted) return;
194
+ const legacy = Object.keys(npmrcAll).filter((k) => k.startsWith("easy-ui5_"));
195
+ if (legacy.length === 0) return;
196
+ legacyWarningEmitted = true;
197
+ // One [WARN]-tagged line on stderr. We instantiate the logger without
198
+ // chalk here to keep this module renderable from tests/non-TTY callers
199
+ // — the tag still surfaces, colour just becomes a no-op.
200
+ const logger = createLogger();
201
+ logger.warn(`Found legacy easy-ui5_* keys in ~/.npmrc (${legacy.join(", ")}). These will be ignored in a future release. Move them to ~/.easyui5rc.json — see README.`);
202
+ }
203
+
204
+ // ---------- merge ----------
205
+
206
+ function loadConfig() {
207
+ const userNpmrcPath = process.env.NPM_CONFIG_USERCONFIG || path.join(homedir(), ".npmrc");
208
+ const globalNpmrcPath = process.env.NPM_CONFIG_GLOBALCONFIG;
209
+ const projectNpmrcPath = path.join(process.cwd(), ".npmrc");
210
+
211
+ const globalNpmrc = readNpmrc(globalNpmrcPath);
212
+ const userNpmrc = readNpmrc(userNpmrcPath);
213
+ const projectNpmrc = readNpmrc(projectNpmrcPath);
214
+
215
+ // Warn once if legacy easy-ui5_* keys still live in ~/.npmrc.
216
+ maybeWarnLegacyNpmrcKeys(userNpmrc.all);
217
+
218
+ // Build the easy-ui5_* layer with precedence (low -> high):
219
+ // 1. legacy npmrc easy-ui5_* (one-release grace period, deprecated)
220
+ // 2. easyui5rc cascade (canonical home)
221
+ // 3. EASY_UI5_* env vars (highest — handled in getNPMConfig)
222
+ const easyUi5Layer = {};
223
+ // 1. legacy: pull easy-ui5_* out of user/project .npmrc
224
+ for (const npmrcLayer of [userNpmrc.all, projectNpmrc.all]) {
225
+ for (const [k, v] of Object.entries(npmrcLayer)) {
226
+ if (k.startsWith("easy-ui5_")) easyUi5Layer[k] = v;
227
+ }
228
+ }
229
+ // 2. canonical: walk the .easyui5rc cascade (low -> high precedence)
230
+ const cascade = collectEasyUi5RcCascade(process.cwd());
231
+ for (const { data } of cascade) {
232
+ for (const [k, v] of Object.entries(data)) {
233
+ easyUi5Layer[`easy-ui5_${k}`] = v;
234
+ }
235
+ }
236
+
237
+ // Proxy layer from .npmrc (legitimate npm config) and npm_config_*
238
+ const proxyLayer = {
239
+ ...globalNpmrc.proxy,
240
+ ...userNpmrc.proxy,
241
+ ...projectNpmrc.proxy,
242
+ ...readEnvNpmConfig(),
243
+ };
244
+
245
+ const merged = { ...proxyLayer, ...easyUi5Layer };
246
+
247
+ // EASY_UI5_* env vars win over file values. We expand `getNPMConfig` to
248
+ // check these dynamically on each call so we don't need to enumerate
249
+ // every option here.
250
+
251
+ // ${ENV_VAR} interpolation on all string values
252
+ for (const k of Object.keys(merged)) {
253
+ merged[k] = expandEnv(merged[k]);
254
+ }
255
+ return merged;
256
+ }
257
+
258
+ // ---------- public API ----------
259
+
7
260
  export default function getNPMConfig(configName, prefix = "easy-ui5_") {
8
- if (!npmConfig) {
9
- npmConfig = libnpmconfig.read();
261
+ if (!npmConfig) npmConfig = loadConfig();
262
+
263
+ // EASY_UI5_* env vars always win for the easy-ui5_ prefix
264
+ if (prefix === "easy-ui5_") {
265
+ const envName = envVarNameFor(configName);
266
+ const fromEnv = process.env[envName];
267
+ if (fromEnv !== undefined && fromEnv !== "") return fromEnv;
10
268
  }
11
- return npmConfig && npmConfig[`${prefix}${configName}`];
269
+
270
+ return npmConfig[`${prefix}${configName}`];
271
+ }
272
+
273
+ // ---------- test hooks ----------
274
+
275
+ export function _resetCacheForTests() {
276
+ npmConfig = undefined;
277
+ legacyWarningEmitted = false;
12
278
  }
@@ -10,10 +10,12 @@ import url from "url";
10
10
 
11
11
  import { glob } from "glob";
12
12
  import chalk from "chalk";
13
- import yosay from "yosay";
14
13
  import nodeFetch from "node-fetch";
15
14
  import AdmZip from "adm-zip";
16
15
 
16
+ import { renderBanner } from "./banner.js";
17
+ import { createLogger } from "./log.js";
18
+
17
19
  import { request } from "@octokit/request";
18
20
  import { Octokit } from "@octokit/rest";
19
21
  import { throttling } from "@octokit/plugin-throttling";
@@ -31,6 +33,11 @@ export default class EasyUI5Generator extends Generator {
31
33
 
32
34
  defineGeneratorArguments(this);
33
35
  defineGeneratorOptions(this);
36
+
37
+ // Tagged logger: [INFO]/[WARN]/[ERROR]/[OK]/[DEBUG]. DEBUG is shown only
38
+ // when --verbose is set. WARN/ERROR are routed to stderr so they don't
39
+ // get swallowed when stdout is redirected.
40
+ this.logger = createLogger({ chalk, verbose: !!this.options.verbose });
34
41
  }
35
42
 
36
43
  _showBusy(statusText) {
@@ -57,7 +64,11 @@ export default class EasyUI5Generator extends Generator {
57
64
 
58
65
  async _npmInstall(dir, withDevDeps) {
59
66
  return new Promise((resolve, reject) => {
60
- this.spawnCommand("npm", ["install", "--no-progress", "--ignore-engines", "--ignore-scripts"], {
67
+ // yeoman-generator v8+ split the spawn API: `spawnCommand` now takes
68
+ // `(command, execaOptions)` (no args array — it parses the shell
69
+ // string), whereas `spawn` takes `(command, args[], options)`. We
70
+ // pass argv as an array, so we want `spawn`.
71
+ this.spawn("npm", ["install", "--no-progress", "--ignore-engines", "--ignore-scripts", "--prefer-offline"], {
61
72
  stdio: this.config.verbose ? "inherit" : "ignore",
62
73
  cwd: dir,
63
74
  env: {
@@ -118,9 +129,9 @@ export default class EasyUI5Generator extends Generator {
118
129
  });
119
130
  generator.branch = repoInfo.data.default_branch;
120
131
  } catch (e) {
121
- console.error(`Generator "${owner}/${repo}!${dir}${branch ? "#" + branch : ""}" not found! Run with --verbose for details!\n(Hint: ${e.message})`);
132
+ this.logger.error(`Generator "${owner}/${repo}!${dir}${branch ? "#" + branch : ""}" not found! Run with --verbose for details!\n(Hint: ${e.message})`);
122
133
  if (this.options.verbose) {
123
- console.error(e);
134
+ this.logger.debug(String(e?.stack ?? e));
124
135
  }
125
136
  return;
126
137
  }
@@ -136,17 +147,17 @@ export default class EasyUI5Generator extends Generator {
136
147
  });
137
148
  commitSHA = reqBranch.data.commit.sha;
138
149
  } catch (ex) {
139
- console.error(
140
- chalk.red(`Failed to retrieve the branch "${generator.branch}" for repository "${generator.name}" for "${generator.org}" organization! Run with --verbose for details!\n(Hint: ${ex.message})`),
150
+ this.logger.error(
151
+ `Failed to retrieve the branch "${generator.branch}" for repository "${generator.name}" for "${generator.org}" organization! Run with --verbose for details!\n(Hint: ${ex.message})`,
141
152
  );
142
153
  if (this.options.verbose) {
143
- console.error(chalk.red(ex.message));
154
+ this.logger.debug(String(ex?.stack ?? ex.message));
144
155
  }
145
156
  return;
146
157
  }
147
158
 
148
159
  if (this.options.verbose) {
149
- this.log(`Using commit ${commitSHA} from @${generator.org}/${generator.name}#${generator.branch}!`);
160
+ this.logger.debug(`Using commit ${commitSHA} from @${generator.org}/${generator.name}#${generator.branch}!`);
150
161
  }
151
162
  const shaMarker = path.join(generatorPath, `.${commitSHA}`);
152
163
 
@@ -154,7 +165,7 @@ export default class EasyUI5Generator extends Generator {
154
165
  // check if the SHA marker exists to know whether the generator is up-to-date or not
155
166
  if (this.options.forceUpdate || !fs.existsSync(shaMarker)) {
156
167
  if (this.options.verbose) {
157
- this.log(`Generator ${chalk.yellow(generator.name)} in "${generatorPath}" is outdated!`);
168
+ this.logger.debug(`Generator ${chalk.yellow(generator.name)} in "${generatorPath}" is outdated!`);
158
169
  }
159
170
  // remove if the SHA marker doesn't exist => outdated!
160
171
  this._showBusy(` Deleting subgenerator ${chalk.yellow(generator.name)}...`);
@@ -166,7 +177,7 @@ export default class EasyUI5Generator extends Generator {
166
177
  if (!fs.existsSync(generatorPath)) {
167
178
  // unzip the archive
168
179
  if (this.options.verbose) {
169
- this.log(`Extracting ZIP to "${generatorPath}"...`);
180
+ this.logger.debug(`Extracting ZIP to "${generatorPath}"...`);
170
181
  }
171
182
  this._showBusy(` Downloading subgenerator ${chalk.yellow(generator.name)}...`);
172
183
  const reqZIPArchive = await octokit.repos.downloadZipballArchive({
@@ -186,7 +197,7 @@ export default class EasyUI5Generator extends Generator {
186
197
  // run npm install when not embedding the generator (always for self-healing!)
187
198
  if (!this.options.embed) {
188
199
  if (this.options.verbose) {
189
- this.log("Installing the subgenerator dependencies...");
200
+ this.logger.debug("Installing the subgenerator dependencies...");
190
201
  }
191
202
  this._showBusy(` Preparing ${chalk.yellow(generator.name)}...`);
192
203
  await this._npmInstall(generatorPath, this.options.pluginsWithDevDeps);
@@ -199,26 +210,26 @@ export default class EasyUI5Generator extends Generator {
199
210
  const home = path.join(__dirname, "..", "..");
200
211
  const pkgJson = JSON.parse(fs.readFileSync(path.join(home, "package.json"), "utf8"));
201
212
 
202
- // Have Yeoman greet the user.
213
+ // Have Yeoman greet the user — with our own easy-ui5 banner.
203
214
  if (!this.options.embedded) {
204
- this.log(yosay(`Welcome to the ${chalk.red("easy-ui5")} ${chalk.yellow(pkgJson.version)} generator!`));
215
+ this.log(renderBanner(pkgJson.version, chalk));
205
216
  }
206
217
 
207
218
  // by default we install the easy-ui5 plugin generators into the following folder:
208
219
  // %user_dir%/.npm/_generator-easy-ui5/plugin-generators
209
220
  let pluginsHome = this.options.pluginsHome;
210
221
  if (this.options.verbose) {
211
- console.info(" Context:");
212
- console.info(` - sourceRoot = ${chalk.green(this.sourceRoot())}`);
213
- console.info(` - destinationRoot = ${chalk.green(this.destinationRoot())}`);
214
- console.info(" Options:");
222
+ this.logger.debug("Context:");
223
+ this.logger.debug(` sourceRoot = ${chalk.green(this.sourceRoot())}`);
224
+ this.logger.debug(` destinationRoot = ${chalk.green(this.destinationRoot())}`);
225
+ this.logger.debug("Options:");
215
226
  Object.keys(generatorOptions).forEach((option) => {
216
- console.info(` - ${option} = ${chalk.green(this.options[option])}`);
227
+ this.logger.debug(` ${option} = ${chalk.green(this.options[option])}`);
217
228
  });
218
- console.info(" Proxy:");
219
- console.info(` - HTTP_PROXY = ${chalk.green(HTTP_PROXY)}`);
220
- console.info(` - HTTPS_PROXY = ${chalk.green(HTTPS_PROXY)}`);
221
- console.info(` - NO_PROXY = ${chalk.green(NO_PROXY)}`);
229
+ this.logger.debug("Proxy:");
230
+ this.logger.debug(` HTTP_PROXY = ${chalk.green(HTTP_PROXY)}`);
231
+ this.logger.debug(` HTTPS_PROXY = ${chalk.green(HTTPS_PROXY)}`);
232
+ this.logger.debug(` NO_PROXY = ${chalk.green(NO_PROXY)}`);
222
233
  }
223
234
  fs.mkdirSync(pluginsHome, { recursive: true });
224
235
 
@@ -244,7 +255,17 @@ export default class EasyUI5Generator extends Generator {
244
255
  this.log(chalk.green("\nAvailable generators:"));
245
256
  glob.sync(`${pluginsHome}/*/package.json`).forEach((plugin) => {
246
257
  const name = plugin.match(/.*\/generator-(.+)\/package\.json/)[1];
247
- const lib = require(plugin);
258
+ // Resolve and validate the path is within pluginsHome to satisfy eslint security rule.
259
+ const resolvedPlugin = path.resolve(plugin);
260
+ const resolvedPluginsHome = path.resolve(pluginsHome);
261
+ if (!resolvedPlugin.startsWith(resolvedPluginsHome + path.sep)) {
262
+ return; // skip paths outside pluginsHome
263
+ }
264
+ // plugin is a filesystem path under pluginsHome (Yeoman cache).
265
+ // Read its package.json directly rather than require()-ing it,
266
+ // so we never execute arbitrary code from a cached plugin just
267
+ // to print its version.
268
+ const lib = JSON.parse(fs.readFileSync(resolvedPlugin, "utf8"));
248
269
  this.log(` - ${chalk.green(name)}: ${lib.version}`);
249
270
  });
250
271
 
@@ -255,7 +276,7 @@ export default class EasyUI5Generator extends Generator {
255
276
  // when not running in offline mode!
256
277
  let octokit;
257
278
  if (this.options.offline) {
258
- this.log(`Running in ${chalk.yellow("offline")} mode!`);
279
+ this.logger.info(`Running in ${chalk.yellow("offline")} mode!`);
259
280
  } else {
260
281
  // define the options for the Octokit API
261
282
  const octokitOptions = {
@@ -271,18 +292,18 @@ export default class EasyUI5Generator extends Generator {
271
292
  baseUrl: this.options.ghBaseUrl,
272
293
  throttle: {
273
294
  onRateLimit: (retryAfter, options) => {
274
- this.log(`${chalk.yellow("Hit the GitHub API limit!")} Request quota exhausted for this request.`);
295
+ this.logger.warn("Hit the GitHub API limit! Request quota exhausted for this request.");
275
296
  if (options.request.retryCount === 0) {
276
297
  // only retries once
277
- this.log(
278
- `Retrying after ${retryAfter} seconds. Alternatively, you can cancel this operation and supply an auth token with the \`--ghAuthToken\` option. For more details, run \`yo easy-ui5 --help\`. `,
298
+ this.logger.warn(
299
+ `Retrying after ${retryAfter} seconds. Alternatively, you can cancel this operation and supply an auth token with the \`--ghAuthToken\` option. For more details, run \`yo easy-ui5 --help\`.`,
279
300
  );
280
301
  return true;
281
302
  }
282
303
  },
283
304
  onSecondaryRateLimit: () => {
284
- // does not retry, only logs a warning
285
- this.log(`${chalk.red("Hit the GitHub API limit again!")} Please supply an auth token with the \`--ghAuthToken\` option. For more details, run \`yo easy-ui5 --help\` `);
305
+ // does not retry, only logs an error
306
+ this.logger.error("Hit the GitHub API limit again! Please supply an auth token with the `--ghAuthToken` option. For more details, run `yo easy-ui5 --help`.");
286
307
  },
287
308
  },
288
309
  };
@@ -353,7 +374,7 @@ export default class EasyUI5Generator extends Generator {
353
374
  };
354
375
  // log which generator is being used!
355
376
  if (this.options.verbose) {
356
- this.log(`Using generator ${chalk.green(`${owner}/${repo}!${dir}${branch ? "#" + branch : ""}`)}`);
377
+ this.logger.debug(`Using generator ${chalk.green(`${owner}/${repo}!${dir}${branch ? "#" + branch : ""}`)}`);
357
378
  }
358
379
  }
359
380
 
@@ -417,9 +438,9 @@ export default class EasyUI5Generator extends Generator {
417
438
  };
418
439
  });
419
440
  } catch (e) {
420
- console.error(`Failed to connect to bestofui5.org to retrieve all available generators! Run with --verbose for details!\n(Hint: ${e.message})`);
441
+ this.logger.error(`Failed to connect to bestofui5.org to retrieve all available generators! Run with --verbose for details!\n(Hint: ${e.message})`);
421
442
  if (this.options.verbose) {
422
- console.error(e);
443
+ this.logger.debug(String(e?.stack ?? e));
423
444
  }
424
445
  return;
425
446
  }
@@ -428,9 +449,9 @@ export default class EasyUI5Generator extends Generator {
428
449
  try {
429
450
  availGenerators = await listGeneratorsForOrg(this.options.ghOrg, this.options.subGeneratorPrefix, this.options.ghThreshold);
430
451
  } catch (e) {
431
- console.error(`Failed to connect to GitHub to retrieve all available generators for "${this.options.ghOrg}" organization! Run with --verbose for details!\n(Hint: ${e.message})`);
452
+ this.logger.error(`Failed to connect to GitHub to retrieve all available generators for "${this.options.ghOrg}" organization! Run with --verbose for details!\n(Hint: ${e.message})`);
432
453
  if (this.options.verbose) {
433
- console.error(e);
454
+ this.logger.debug(String(e?.stack ?? e));
434
455
  }
435
456
  return;
436
457
  }
@@ -442,14 +463,16 @@ export default class EasyUI5Generator extends Generator {
442
463
  }
443
464
  } catch (e) {
444
465
  if (this.options.verbose) {
445
- this.log(`Failed to connect to GitHub retrieve additional generators for "${this.options.addGhOrg}" organization! Try to retrieve for user...`);
466
+ this.logger.debug(`Failed to connect to GitHub retrieve additional generators for "${this.options.addGhOrg}" organization! Try to retrieve for user...`);
446
467
  }
447
468
  try {
448
469
  availGenerators = availGenerators.concat(await listGeneratorsForUser(this.options.addGhOrg, this.options.addSubGeneratorPrefix, this.options.ghThreshold));
449
470
  } catch (e1) {
450
- console.error(`Failed to connect to GitHub to retrieve additional generators for organization or user "${this.options.addGhOrg}"! Run with --verbose for details!\n(Hint: ${e.message})`);
471
+ this.logger.error(
472
+ `Failed to connect to GitHub to retrieve additional generators for organization or user "${this.options.addGhOrg}"! Run with --verbose for details!\n(Hint: ${e.message})`,
473
+ );
451
474
  if (this.options.verbose) {
452
- console.error(e1);
475
+ this.logger.debug(String(e1?.stack ?? e1));
453
476
  }
454
477
  return;
455
478
  }
@@ -465,7 +488,7 @@ export default class EasyUI5Generator extends Generator {
465
488
 
466
489
  // if no generator is provided and doesn't exist, ask for generator name
467
490
  if (this.options.generator && !generator) {
468
- this.log(`The generator ${chalk.red(this.options.generator)} was not found. Please select an existing generator!`);
491
+ this.logger.error(`The generator ${chalk.red(this.options.generator)} was not found. Please select an existing generator!`);
469
492
  }
470
493
 
471
494
  // still not found, select a generator
@@ -552,7 +575,7 @@ export default class EasyUI5Generator extends Generator {
552
575
  if (selectedSubGenerator.length == 1) {
553
576
  subGenerators = selectedSubGenerator;
554
577
  } else {
555
- this.log(`The generator ${chalk.red(this.options.generator)} has no subcommand ${chalk.red(this.options.subcommand)}. Please select an existing subcommand!`);
578
+ this.logger.error(`The generator ${chalk.red(this.options.generator)} has no subcommand ${chalk.red(this.options.subcommand)}. Please select an existing subcommand!`);
556
579
  }
557
580
  }
558
581
 
@@ -629,7 +652,7 @@ export default class EasyUI5Generator extends Generator {
629
652
  subGensToRun.push(theNestedGen.namespace);
630
653
  await resolveNestedGenerator(theNestedGen.namespace);
631
654
  } else {
632
- this.log(`The nested generator "${nestedGeneratorInfo.org}/${nestedGeneratorInfo.name}" has no subgenerator "${subcommand || "default"}"! Ignoring execution...`);
655
+ this.logger.warn(`The nested generator "${nestedGeneratorInfo.org}/${nestedGeneratorInfo.name}" has no subgenerator "${subcommand || "default"}"! Ignoring execution...`);
633
656
  }
634
657
  }
635
658
  }) || [],
@@ -658,7 +681,7 @@ export default class EasyUI5Generator extends Generator {
658
681
  };
659
682
 
660
683
  if (this.options.verbose) {
661
- this.log(`Running generators in "${generatorPath}"...`);
684
+ this.logger.debug(`Running generators in "${generatorPath}"...`);
662
685
  }
663
686
 
664
687
  // chain the execution of the generators
@@ -690,20 +713,20 @@ export default class EasyUI5Generator extends Generator {
690
713
  });
691
714
  */
692
715
  } else {
693
- this.log(`The generator ${chalk.red(this.options.generator)} has no visible subgenerators!`);
716
+ this.logger.error(`The generator ${chalk.red(this.options.generator)} has no visible subgenerators!`);
694
717
  }
695
718
  } else {
696
719
  // zip the content of the plugin generator or
697
720
  // install the dependencies of the generator
698
721
  if (this.options.verbose) {
699
- this.log(`Embedding plugin generator ${chalk.yellow(generator.name)}...`);
722
+ this.logger.debug(`Embedding plugin generator ${chalk.yellow(generator.name)}...`);
700
723
  }
701
724
  const generatorZIP = new AdmZip();
702
725
  const addLocalFile = (file) => {
703
726
  const filePath = path.join(generator.name, path.relative(generatorPath, file));
704
727
  generatorZIP.addLocalFile(file, path.dirname(filePath), path.basename(filePath));
705
728
  if (this.options.verbose) {
706
- this.log(` + file: ${file}`);
729
+ this.logger.debug(` + file: ${file}`);
707
730
  }
708
731
  };
709
732
  glob.sync(`${generatorPath}/*`, { nodir: true, dot: true }).forEach(addLocalFile);
@@ -711,20 +734,22 @@ export default class EasyUI5Generator extends Generator {
711
734
  const generatorZIPPath = path.join(__dirname, "../../plugins", `${generator.name}.zip`);
712
735
  generatorZIP.writeZip(generatorZIPPath);
713
736
  if (this.options.verbose) {
714
- this.log(`Stored plugin generator ${chalk.yellow(generator.name)} zip to "${generatorZIPPath}"!`);
737
+ this.logger.ok(`Stored plugin generator ${chalk.yellow(generator.name)} zip to "${generatorZIPPath}"!`);
715
738
  }
716
739
  }
717
740
  }
718
741
 
719
742
  end() {
720
743
  if (this.config.get("initrepo")) {
721
- this.spawnCommandSync("git", ["init", "--quiet"], {
744
+ // yeoman-generator v8+: `spawnSync` is the arg-array variant;
745
+ // `spawnCommandSync` would parse a single shell string instead.
746
+ this.spawnSync("git", ["init", "--quiet"], {
722
747
  cwd: this.destinationPath(),
723
748
  });
724
- this.spawnCommandSync("git", ["add", "."], {
749
+ this.spawnSync("git", ["add", "."], {
725
750
  cwd: this.destinationPath(),
726
751
  });
727
- this.spawnCommandSync("git", ["commit", "--quiet", "--allow-empty", "-m", "Initial commit"], {
752
+ this.spawnSync("git", ["commit", "--quiet", "--allow-empty", "-m", "Initial commit"], {
728
753
  cwd: this.destinationPath(),
729
754
  });
730
755
  }
@@ -0,0 +1,90 @@
1
+ // Tagged logger used across the easy-ui5 runtime.
2
+ //
3
+ // Each call is prefixed with a fixed-width category tag (`[INFO ]`, `[WARN ]`,
4
+ // `[ERROR]`, `[OK ]`, `[DEBUG]`) so messages line up in the user's terminal.
5
+ // The tag is coloured by severity; the message body is passed through
6
+ // untouched so existing call sites that already paint individual words with
7
+ // chalk keep working.
8
+ //
9
+ // Routing follows POSIX conventions: `ERROR` and `WARN` go to stderr, everything
10
+ // else to stdout. `DEBUG` is suppressed unless `verbose` is enabled.
11
+ //
12
+ // The logger takes its colorizer (chalk-like) and its sinks by injection so
13
+ // tests can render deterministically and capture output without monkey-patching
14
+ // globals.
15
+
16
+ const TAG_WIDTH = 5; // longest tag name: "DEBUG" / "ERROR"
17
+
18
+ const LEVELS = {
19
+ debug: { name: "DEBUG", colour: "gray", stream: "stdout" },
20
+ info: { name: "INFO", colour: "cyan", stream: "stdout" },
21
+ ok: { name: "OK", colour: "green", stream: "stdout" },
22
+ warn: { name: "WARN", colour: "yellow", stream: "stderr" },
23
+ error: { name: "ERROR", colour: "red", stream: "stderr" },
24
+ };
25
+
26
+ function pad(name) {
27
+ return name.padEnd(TAG_WIDTH, " ");
28
+ }
29
+
30
+ /**
31
+ * Build a tagged logger.
32
+ *
33
+ * @param {object} [opts]
34
+ * @param {object} [opts.chalk] chalk-like colorizer. When omitted the logger
35
+ * renders without ANSI escapes — handy for tests / non-TTY pipes.
36
+ * @param {boolean} [opts.verbose] Include DEBUG messages when true. Off by default.
37
+ * @param {(s:string)=>void} [opts.stdout] Stdout sink. Defaults to writing to `process.stdout` with a trailing newline.
38
+ * @param {(s:string)=>void} [opts.stderr] Stderr sink. Defaults to writing to `process.stderr` with a trailing newline.
39
+ */
40
+ export function createLogger(opts = {}) {
41
+ const chalk = opts.chalk;
42
+ const verbose = !!opts.verbose;
43
+ const stdout = opts.stdout ?? ((line) => process.stdout.write(`${line}\n`));
44
+ const stderr = opts.stderr ?? ((line) => process.stderr.write(`${line}\n`));
45
+
46
+ function colour(name, text) {
47
+ const fn = chalk?.[name];
48
+ return typeof fn === "function" ? fn(text) : text;
49
+ }
50
+
51
+ function format(levelKey, message) {
52
+ const lvl = LEVELS[levelKey];
53
+ const tag = `[${pad(lvl.name)}]`;
54
+ const colouredTag = colour(lvl.colour, tag);
55
+ return `${colouredTag} ${message}`;
56
+ }
57
+
58
+ function emit(levelKey, message) {
59
+ if (levelKey === "debug" && !verbose) return;
60
+ const lvl = LEVELS[levelKey];
61
+ const sink = lvl.stream === "stderr" ? stderr : stdout;
62
+ sink(format(levelKey, message));
63
+ }
64
+
65
+ return {
66
+ debug(...args) {
67
+ emit("debug", args.join(" "));
68
+ },
69
+ info(...args) {
70
+ emit("info", args.join(" "));
71
+ },
72
+ ok(...args) {
73
+ emit("ok", args.join(" "));
74
+ },
75
+ warn(...args) {
76
+ emit("warn", args.join(" "));
77
+ },
78
+ error(...args) {
79
+ emit("error", args.join(" "));
80
+ },
81
+ /**
82
+ * Print a line WITHOUT a category tag. Use for cosmetic output such as
83
+ * the welcome banner, headers, and the "verbose context" dump where a
84
+ * `[INFO]` prefix would just add noise.
85
+ */
86
+ plain(...args) {
87
+ stdout(args.join(" "));
88
+ },
89
+ };
90
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-easy-ui5",
3
- "version": "3.9.1",
3
+ "version": "3.10.0",
4
4
  "description": "Generator for UI5-based project",
5
5
  "main": "generators/app/index.js",
6
6
  "type": "module",
@@ -9,18 +9,23 @@
9
9
  "plugins"
10
10
  ],
11
11
  "engines": {
12
- "node": ">=20"
12
+ "node": ">=20.19"
13
13
  },
14
14
  "scripts": {
15
- "prepublishOnly": "npx yo@5.1.0 ./generators/app/index.js project --embed",
15
+ "embed": "npx yo@7.0.1 ./generators/app/index.js project --embed",
16
16
  "start": "yo easy-ui5 project",
17
17
  "test": "mocha",
18
18
  "test:subgen:list": "yo easy-ui5 project --list",
19
19
  "test:subgen:start": "yo easy-ui5 project app",
20
- "release:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
21
- "postversion": "npm run release:changelog && git commit --all --amend --no-edit",
20
+ "changeset": "changeset",
21
+ "changeset:auto": "node ./scripts/auto-changeset.mjs",
22
+ "changeset:empty": "changeset --empty",
23
+ "changeset:status": "changeset status --since=origin/main",
24
+ "version-packages": "changeset version && npm install --package-lock-only --ignore-scripts",
25
+ "release-publish": "changeset publish",
22
26
  "lint": "eslint .",
23
27
  "lint:fix": "eslint . --fix",
28
+ "lint:ci": "eslint . --max-warnings 0",
24
29
  "lint:staged": "lint-staged",
25
30
  "format": "prettier --write .",
26
31
  "format:staged": "pretty-quick --staged --verbose",
@@ -49,38 +54,42 @@
49
54
  "homepage": "https://github.com/ui5-community/generator-easy-ui5#readme",
50
55
  "dependencies": {
51
56
  "@octokit/plugin-throttling": "^11.0.3",
57
+ "@octokit/request": "^10.0.10",
52
58
  "@octokit/rest": "^22.0.1",
53
- "adm-zip": "^0.5.16",
59
+ "adm-zip": "^0.5.17",
54
60
  "chalk": "^5.6.2",
55
- "colors": "^1.4.0",
56
- "glob": "^13.0.0",
57
- "libnpmconfig": "^1.2.1",
61
+ "glob": "^13.0.6",
58
62
  "node-fetch": "^3.3.2",
59
- "rimraf": "^6.1.2",
60
- "yeoman-environment": "^5.1.2",
61
- "yeoman-generator": "^7.5.1",
62
- "yosay": "^3.0.0"
63
+ "rimraf": "^6.1.3",
64
+ "yeoman-environment": "^6.1.0",
65
+ "yeoman-generator": "^8.2.2"
63
66
  },
64
67
  "devDependencies": {
68
+ "@changesets/changelog-github": "^0.7.0",
69
+ "@changesets/cli": "^2.31.0",
65
70
  "@commitlint/cli": "^20.2.0",
66
71
  "@commitlint/config-conventional": "^20.2.0",
67
- "conventional-changelog-cli": "^5.0.0",
68
- "cz-conventional-changelog": "^3.3.0",
69
- "eslint": "^9.39.2",
70
- "fs-extra": "^11.3.3",
72
+ "@eslint/js": "^10.0.1",
73
+ "eslint": "^10.5.0",
74
+ "eslint-plugin-n": "^18.1.0",
75
+ "eslint-plugin-security": "^4.0.1",
76
+ "fs-extra": "^11.3.5",
77
+ "globals": "^17.6.0",
71
78
  "husky": "^9.1.7",
72
- "lint-staged": "^16.2.7",
73
- "mocha": "^11.7.5",
74
- "npm-run-all": "^4.1.5",
75
- "prettier": "^3.7.4",
79
+ "lint-staged": "^16.4.0",
80
+ "mocha": "^11.7.6",
81
+ "prettier": "^3.8.4",
76
82
  "pretty-quick": "^4.2.2",
77
83
  "yeoman-assert": "^3.1.1",
78
- "yeoman-test": "^11.2.0"
84
+ "yeoman-test": "^11.6.0",
85
+ "yosay": "^3.0.0"
79
86
  },
80
- "config": {
81
- "commitizen": {
82
- "path": "cz-conventional-changelog"
83
- }
87
+ "overrides": {
88
+ "serialize-javascript": "^7.0.5",
89
+ "diff": "^8.0.3",
90
+ "conventional-commits-parser": "^6.4.0",
91
+ "js-yaml": "^4.2.0",
92
+ "tar": "^7.5.16"
84
93
  },
85
94
  "commitlint": {
86
95
  "extends": [
Binary file