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 +75 -1
- package/generators/app/banner.js +92 -0
- package/generators/app/getNPMConfig.js +272 -6
- package/generators/app/index.js +73 -48
- package/generators/app/log.js +90 -0
- package/package.json +35 -26
- package/plugins/generator-ui5-project.zip +0 -0
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
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
269
|
+
|
|
270
|
+
return npmConfig[`${prefix}${configName}`];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ---------- test hooks ----------
|
|
274
|
+
|
|
275
|
+
export function _resetCacheForTests() {
|
|
276
|
+
npmConfig = undefined;
|
|
277
|
+
legacyWarningEmitted = false;
|
|
12
278
|
}
|
package/generators/app/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
227
|
+
this.logger.debug(` ${option} = ${chalk.green(this.options[option])}`);
|
|
217
228
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
285
|
-
this.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
749
|
+
this.spawnSync("git", ["add", "."], {
|
|
725
750
|
cwd: this.destinationPath(),
|
|
726
751
|
});
|
|
727
|
-
this.
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
21
|
-
"
|
|
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.
|
|
59
|
+
"adm-zip": "^0.5.17",
|
|
54
60
|
"chalk": "^5.6.2",
|
|
55
|
-
"
|
|
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.
|
|
60
|
-
"yeoman-environment": "^
|
|
61
|
-
"yeoman-generator": "^
|
|
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
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"eslint": "^
|
|
70
|
-
"
|
|
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.
|
|
73
|
-
"mocha": "^11.7.
|
|
74
|
-
"
|
|
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.
|
|
84
|
+
"yeoman-test": "^11.6.0",
|
|
85
|
+
"yosay": "^3.0.0"
|
|
79
86
|
},
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
|
|
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
|