@codejen/pack-yeoman 0.0.0-stage → 2.0.1
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 +23 -2
- package/dist/common.d.ts +36 -0
- package/dist/common.js +132 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/dist/jhipster.d.ts +4 -0
- package/dist/jhipster.js +125 -0
- package/dist/shim.d.ts +4 -0
- package/dist/shim.js +88 -0
- package/package.json +45 -4
package/README.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @codejen/pack-yeoman
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A [jen](https://learosema.github.io/jen/) pack that runs [Yeoman](https://yeoman.io) generators – [JHipster](https://www.jhipster.tech) included – through jen's plan: never interactive, never online, and nothing is written before you see it.
|
|
4
|
+
|
|
5
|
+
**Documentation: <https://learosema.github.io/jen/yeoman.html>**
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install --save-dev @codejen/pack-yeoman generator-code # or: npm install -g @codejen/jen @codejen/pack-yeoman generator-code
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
jen yeoman:code --extensionType=command-ts --extensionDisplayName="My Extension"
|
|
15
|
+
jen yeoman:jhipster --defaults --base-name=blog
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Credits
|
|
19
|
+
|
|
20
|
+
[Yeoman](https://yeoman.io) by the Yeoman team, and [JHipster](https://www.jhipster.tech) by the JHipster team, whose environment builder and deferred commit the pack runs JHipster with.
|
|
21
|
+
|
|
22
|
+
## License
|
|
23
|
+
|
|
24
|
+
ISC © Lea Rosema
|
package/dist/common.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** What every Yeoman runner shares: jen's flags as Yeoman sees them, prompts answered without prompting, mem-fs files as jen actions. */
|
|
2
|
+
import type { Action, Args, Context, PackEnv } from '@codejen/jen';
|
|
3
|
+
export interface Question {
|
|
4
|
+
name: string;
|
|
5
|
+
default?: unknown;
|
|
6
|
+
when?: unknown;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface MemFsFile {
|
|
10
|
+
path: string;
|
|
11
|
+
contents: Buffer | null;
|
|
12
|
+
state?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Generators jen may fetch on the fly: a fixed list, so a typo (yeoman:jhispter) can never install someone else's package. */
|
|
15
|
+
export declare const FETCHABLE: readonly string[];
|
|
16
|
+
/** Whether `name` is installed – by its package.json, since a generator package's main may not resolve. */
|
|
17
|
+
export declare const isInstalled: (env: PackEnv, name: string) => boolean;
|
|
18
|
+
/** The first of `paths` (in package `name`) that resolves – after fetching `name` for this run if it's on the list and not installed. */
|
|
19
|
+
export declare function locate(env: PackEnv, name: string, paths?: string[]): Promise<string>;
|
|
20
|
+
/** The raw flags and positionals – jen fills them in since 3.0. */
|
|
21
|
+
export declare function argsOf(ctx: Context): Args;
|
|
22
|
+
/** Absolute directory the files go to: the current directory, or --dir. */
|
|
23
|
+
export declare const destination: (ctx: Context) => string;
|
|
24
|
+
export declare const camel: (s: string) => string;
|
|
25
|
+
/** Answers questions from CLI flags (as typed, or camelCased) or the question's own default. Never prompts. */
|
|
26
|
+
export declare function answerPrompt(questions: Question | Question[], flags: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
27
|
+
/** jen's flags back as a command line, for a generator's own option parser. */
|
|
28
|
+
export declare const toArgv: ({ flags, positionals }: Args) => string[];
|
|
29
|
+
/**
|
|
30
|
+
* Runs a generator with the network cut off – fetch() and every TCP connection
|
|
31
|
+
* fail – and fails the run if it tried, even when the generator swallowed the
|
|
32
|
+
* error: whatever a generator downloads or sends would bypass jen's plan.
|
|
33
|
+
*/
|
|
34
|
+
export declare function offline<T>(run: () => Promise<T>): Promise<T>;
|
|
35
|
+
/** The files a generator left pending in mem-fs, as jen actions with paths from the project root. */
|
|
36
|
+
export declare function toActions(files: MemFsFile[], root: string): Action[];
|
package/dist/common.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Socket } from 'node:net';
|
|
2
|
+
import { join, relative, sep } from 'node:path';
|
|
3
|
+
/** Generators jen may fetch on the fly: a fixed list, so a typo (yeoman:jhispter) can never install someone else's package. */
|
|
4
|
+
export const FETCHABLE = ['generator-code', 'generator-jhipster'];
|
|
5
|
+
/** Whether `name` is installed – by its package.json, since a generator package's main may not resolve. */
|
|
6
|
+
export const isInstalled = (env, name) => env.resolve(`${name}/package.json`) !== null;
|
|
7
|
+
/** The first of `paths` (in package `name`) that resolves – after fetching `name` for this run if it's on the list and not installed. */
|
|
8
|
+
export async function locate(env, name, paths = [name]) {
|
|
9
|
+
const first = () => paths.map((p) => env.resolve(p)).find((f) => f !== null) ?? null;
|
|
10
|
+
const installed = isInstalled(env, name);
|
|
11
|
+
if (!installed && !FETCHABLE.includes(name))
|
|
12
|
+
throw new Error(`${name} is listed but not installed – run npm install.`);
|
|
13
|
+
if (!installed && (await env.fetch(name)) === null && !isInstalled(env, name)) {
|
|
14
|
+
throw new Error(`${name} isn't installed, and fetching is off (JEN_NO_FETCH).`);
|
|
15
|
+
}
|
|
16
|
+
return first() ?? fail(`${name} has no generator at ${paths.join(', ')}.`);
|
|
17
|
+
}
|
|
18
|
+
const fail = (message) => {
|
|
19
|
+
throw new Error(message);
|
|
20
|
+
};
|
|
21
|
+
/** The raw flags and positionals – jen fills them in since 3.0. */
|
|
22
|
+
export function argsOf(ctx) {
|
|
23
|
+
if (!ctx.args)
|
|
24
|
+
throw new Error('@codejen/pack-yeoman needs @codejen/jen 3 or later.');
|
|
25
|
+
return ctx.args;
|
|
26
|
+
}
|
|
27
|
+
/** Absolute directory the files go to: the current directory, or --dir. */
|
|
28
|
+
export const destination = (ctx) => join(ctx.root, ctx.destDir);
|
|
29
|
+
export const camel = (s) => s.replace(/-+([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
30
|
+
/** What pressing enter would answer: the default – for a list, the choice it points to or the first one; for a confirm, yes. */
|
|
31
|
+
function defaultOf(q) {
|
|
32
|
+
const choices = Array.isArray(q.choices) ? q.choices.filter((c) => typeof c === 'string' || c.type !== 'separator') : [];
|
|
33
|
+
const value = (c) => (typeof c === 'object' ? (c.value ?? c.name) : c);
|
|
34
|
+
const list = ['list', 'rawlist', 'select', 'expand'].includes(String(q.type));
|
|
35
|
+
if (list && typeof q.default === 'number')
|
|
36
|
+
return value(choices[q.default]);
|
|
37
|
+
if (q.default !== undefined)
|
|
38
|
+
return q.default;
|
|
39
|
+
if (list)
|
|
40
|
+
return value(choices[0]);
|
|
41
|
+
if (q.type === 'confirm')
|
|
42
|
+
return true;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
/** Answers questions from CLI flags (as typed, or camelCased) or the question's own default. Never prompts. */
|
|
46
|
+
export async function answerPrompt(questions, flags) {
|
|
47
|
+
const given = new Map(Object.entries(flags).map(([k, v]) => [camel(k), v]));
|
|
48
|
+
const answers = {};
|
|
49
|
+
const missing = [];
|
|
50
|
+
for (const q of Array.isArray(questions) ? questions : [questions]) {
|
|
51
|
+
const when = typeof q.when === 'function' ? await q.when(answers) : (q.when ?? true);
|
|
52
|
+
if (!when)
|
|
53
|
+
continue;
|
|
54
|
+
if (given.has(camel(q.name))) {
|
|
55
|
+
answers[q.name] = given.get(camel(q.name));
|
|
56
|
+
}
|
|
57
|
+
else if (typeof q.default === 'function') {
|
|
58
|
+
answers[q.name] = await q.default(answers);
|
|
59
|
+
}
|
|
60
|
+
else if (defaultOf(q) !== undefined) {
|
|
61
|
+
answers[q.name] = defaultOf(q);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
missing.push(q.name);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (missing.length > 0) {
|
|
68
|
+
throw new Error(`Missing: ${missing.map((n) => `--${n}`).join(', ')} (asked by the generator's prompting step; jen never prompts).`);
|
|
69
|
+
}
|
|
70
|
+
return answers;
|
|
71
|
+
}
|
|
72
|
+
/** jen's flags back as a command line, for a generator's own option parser. */
|
|
73
|
+
export const toArgv = ({ flags, positionals }) => [
|
|
74
|
+
...Object.entries(flags).flatMap(([name, value]) => (typeof value === 'boolean' ? (value ? [`--${name}`] : []) : [`--${name}=${value}`])),
|
|
75
|
+
...positionals,
|
|
76
|
+
];
|
|
77
|
+
/**
|
|
78
|
+
* Runs a generator with the network cut off – fetch() and every TCP connection
|
|
79
|
+
* fail – and fails the run if it tried, even when the generator swallowed the
|
|
80
|
+
* error: whatever a generator downloads or sends would bypass jen's plan.
|
|
81
|
+
*/
|
|
82
|
+
export async function offline(run) {
|
|
83
|
+
const tried = [];
|
|
84
|
+
const { fetch } = globalThis;
|
|
85
|
+
const { connect } = Socket.prototype;
|
|
86
|
+
globalThis.fetch = async (input) => {
|
|
87
|
+
const url = input instanceof Request ? input.url : String(input);
|
|
88
|
+
tried.push(url);
|
|
89
|
+
throw new Error(`${url}: Yeoman generators run offline under jen.`);
|
|
90
|
+
};
|
|
91
|
+
Socket.prototype.connect = function (...args) {
|
|
92
|
+
const [target] = args;
|
|
93
|
+
const where = typeof target === 'object' ? (target.path ?? `${target.host ?? 'localhost'}:${target.port}`) : String(target);
|
|
94
|
+
tried.push(where);
|
|
95
|
+
process.nextTick(() => this.destroy(new Error(`${where}: Yeoman generators run offline under jen.`)));
|
|
96
|
+
return this;
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
const result = await run();
|
|
100
|
+
if (tried.length > 0)
|
|
101
|
+
throw new Error(`The generator tried to reach ${[...new Set(tried)].join(', ')} – Yeoman generators run offline under jen.`);
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
globalThis.fetch = fetch;
|
|
106
|
+
Socket.prototype.connect = connect;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
110
|
+
/** Text if it's valid UTF-8 without NUL bytes, else the bytes as they are. */
|
|
111
|
+
function decode(contents) {
|
|
112
|
+
try {
|
|
113
|
+
const text = utf8.decode(contents);
|
|
114
|
+
return text.includes('\0') ? contents : text;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return contents;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** The files a generator left pending in mem-fs, as jen actions with paths from the project root. */
|
|
121
|
+
export function toActions(files, root) {
|
|
122
|
+
const fromRoot = (path) => `/${relative(root, path).split(sep).join('/')}`;
|
|
123
|
+
return files.flatMap((file) => {
|
|
124
|
+
if (file.state === 'modified') {
|
|
125
|
+
const contents = decode(file.contents ?? Buffer.alloc(0));
|
|
126
|
+
return [typeof contents === 'string' ? { add: fromRoot(file.path), template: contents } : { add: fromRoot(file.path), bytes: contents }];
|
|
127
|
+
}
|
|
128
|
+
if (file.state === 'deleted')
|
|
129
|
+
return [{ delete: fromRoot(file.path) }];
|
|
130
|
+
return [];
|
|
131
|
+
});
|
|
132
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FETCHABLE, isInstalled } from "./common.js";
|
|
2
|
+
import { jhipsterGenerator } from "./jhipster.js";
|
|
3
|
+
import { shimGenerator } from "./shim.js";
|
|
4
|
+
/** Yeoman's package naming: "generator-code" or "@scope/generator-code" → "code". */
|
|
5
|
+
const YEOMAN_RE = /^(?:@[^/]+\/)?generator-(.+)$/;
|
|
6
|
+
const pack = (env) => {
|
|
7
|
+
const generators = {};
|
|
8
|
+
for (const name of new Set([...env.packages(YEOMAN_RE), ...FETCHABLE])) {
|
|
9
|
+
const short = YEOMAN_RE.exec(name)?.[1] ?? name;
|
|
10
|
+
if (short in generators)
|
|
11
|
+
continue;
|
|
12
|
+
const fetched = isInstalled(env, name) ? '' : ' – fetched when run';
|
|
13
|
+
generators[short] =
|
|
14
|
+
name === 'generator-jhipster'
|
|
15
|
+
? jhipsterGenerator(env, `JHipster: an app, or a command like jdl${fetched}`)
|
|
16
|
+
: shimGenerator(name, short, env, `Yeoman ${name}${fetched}`);
|
|
17
|
+
}
|
|
18
|
+
return generators;
|
|
19
|
+
};
|
|
20
|
+
export default pack;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Runs JHipster in its own real yeoman-environment, keeping what it writes in memory for jen's plan. */
|
|
2
|
+
import type { AnyGenerator, PackEnv } from '@codejen/jen';
|
|
3
|
+
/** JHipster's generators, by the same commands as its own CLI. */
|
|
4
|
+
export declare function jhipsterGenerator(env: PackEnv, description: string): AnyGenerator;
|
package/dist/jhipster.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { answerPrompt, argsOf, camel, locate, offline, destination, toActions } from "./common.js";
|
|
14
|
+
const PRETTIER_CONFIG = /(?:^|[/\\])(?:\.prettierrc[^/\\]*|\.editorconfig)$/;
|
|
15
|
+
/**
|
|
16
|
+
* JHipster's own commit applies prettier and eslint; with deferCommit it
|
|
17
|
+
* leaves the results pending instead of writing them. Without a custom
|
|
18
|
+
* commit task, nothing is committed at all.
|
|
19
|
+
*/
|
|
20
|
+
function deferCommits(env) {
|
|
21
|
+
env.queueCommit = function queueCommit() {
|
|
22
|
+
this.queueTask('environment:conflicts', async () => {
|
|
23
|
+
const requeue = (path) => {
|
|
24
|
+
const state = this.sharedFs.get(path).state;
|
|
25
|
+
if (state === 'modified' || state === 'deleted') {
|
|
26
|
+
this.sharedFs.removeListener('change', requeue);
|
|
27
|
+
this.queueCommit();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
this.sharedFs.on('change', requeue);
|
|
31
|
+
const task = this.composedStore.customCommitTask;
|
|
32
|
+
if (typeof task === 'function')
|
|
33
|
+
await task();
|
|
34
|
+
}, { once: 'write memory fs to disk' });
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Drops install, end and every custom priority after install – they assume the files are on disk. */
|
|
38
|
+
function skipFromInstall(env) {
|
|
39
|
+
const queueTask = env.queueTask.bind(env);
|
|
40
|
+
env.queueTask = (queueName, fn, options) => {
|
|
41
|
+
const names = env.runLoop.queueNames;
|
|
42
|
+
const install = names.indexOf('install');
|
|
43
|
+
if (install >= 0 && names.indexOf(queueName) >= install)
|
|
44
|
+
return;
|
|
45
|
+
queueTask(queueName, fn, options);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Prettier finds its config on disk, relative to the working directory. jen
|
|
50
|
+
* runs JHipster from a scratch directory that mirrors only the config files
|
|
51
|
+
* it generates, so nothing lands in the project before the plan is applied.
|
|
52
|
+
*/
|
|
53
|
+
function mirrorPrettierConfig(env, cwd, scratch) {
|
|
54
|
+
for (const name of existsSync(cwd) ? readdirSync(cwd) : []) {
|
|
55
|
+
if (PRETTIER_CONFIG.test(name))
|
|
56
|
+
copyFileSync(join(cwd, name), join(scratch, name));
|
|
57
|
+
}
|
|
58
|
+
env.sharedFs.on('change', (path) => {
|
|
59
|
+
const file = env.sharedFs.get(path);
|
|
60
|
+
// mem-fs takes a new file's base from the working directory: keep it the destination
|
|
61
|
+
file.cwd = cwd;
|
|
62
|
+
file.base = cwd;
|
|
63
|
+
const rel = relative(cwd, path);
|
|
64
|
+
if (!PRETTIER_CONFIG.test(path) || rel.startsWith('..') || !file.contents)
|
|
65
|
+
return;
|
|
66
|
+
mkdirSync(dirname(join(scratch, rel)), { recursive: true });
|
|
67
|
+
writeFileSync(join(scratch, rel), file.contents);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** JHipster asks npm for its latest version, for an update notice: skipped, without turning off its other checks (skipChecks). */
|
|
71
|
+
async function skipVersionCheck(baseCoreFile) {
|
|
72
|
+
const { default: BaseCore } = (await import(__rewriteRelativeImportExtension(pathToFileURL(baseCoreFile).href)));
|
|
73
|
+
const { checkForNewVersion } = BaseCore.prototype;
|
|
74
|
+
BaseCore.prototype.checkForNewVersion = async () => undefined;
|
|
75
|
+
return () => {
|
|
76
|
+
BaseCore.prototype.checkForNewVersion = checkForNewVersion;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function run(cliFile, baseCoreFile, ctx) {
|
|
80
|
+
const args = argsOf(ctx);
|
|
81
|
+
const here = process.cwd();
|
|
82
|
+
const cwd = destination(ctx);
|
|
83
|
+
const { EnvironmentBuilder } = (await import(__rewriteRelativeImportExtension(pathToFileURL(cliFile).href)));
|
|
84
|
+
// blueprints that aren't installed would be fetched by name from the generator's config, outside jen's plan
|
|
85
|
+
const builder = await EnvironmentBuilder.create({ cwd, skipInstall: true }).prepare({ disableBlueprints: true });
|
|
86
|
+
const env = builder.getEnvironment();
|
|
87
|
+
env.adapter.prompt = async (questions) => answerPrompt(questions, args.flags);
|
|
88
|
+
deferCommits(env);
|
|
89
|
+
skipFromInstall(env);
|
|
90
|
+
// "jen yeoman:jhipster entity Foo" is "jhipster entity Foo"; files given are relative to where jen runs
|
|
91
|
+
const [first, ...rest] = args.positionals;
|
|
92
|
+
const command = first && env.getGeneratorMeta(`jhipster:${first}`) ? first : 'app';
|
|
93
|
+
const positionals = (command === first ? rest : args.positionals).map((p) => (existsSync(resolve(here, p)) ? resolve(here, p) : p));
|
|
94
|
+
const scratch = mkdtempSync(join(tmpdir(), 'jen-jhipster-'));
|
|
95
|
+
mirrorPrettierConfig(env, cwd, scratch);
|
|
96
|
+
const restore = await skipVersionCheck(baseCoreFile);
|
|
97
|
+
process.chdir(scratch);
|
|
98
|
+
try {
|
|
99
|
+
// like JHipster's own CLI: options as an object, camelCased
|
|
100
|
+
await env.run([`jhipster:${command}`, ...positionals], {
|
|
101
|
+
...Object.fromEntries(Object.entries(args.flags).map(([k, v]) => [camel(k), v])),
|
|
102
|
+
skipInstall: true,
|
|
103
|
+
skipGit: true,
|
|
104
|
+
deferCommit: true,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
restore();
|
|
109
|
+
process.chdir(here);
|
|
110
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
return toActions(env.sharedFs.all(), ctx.root);
|
|
113
|
+
}
|
|
114
|
+
/** JHipster's generators, by the same commands as its own CLI. */
|
|
115
|
+
export function jhipsterGenerator(env, description) {
|
|
116
|
+
return {
|
|
117
|
+
description,
|
|
118
|
+
passthrough: true,
|
|
119
|
+
actions: async (_answers, _helpers, ctx) => {
|
|
120
|
+
const cliFile = await locate(env, 'generator-jhipster', ['generator-jhipster/cli']);
|
|
121
|
+
const baseCoreFile = await locate(env, 'generator-jhipster', ['generator-jhipster/generators/base-core']);
|
|
122
|
+
return offline(() => run(cliFile, baseCoreFile, ctx));
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
package/dist/shim.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Runs a Yeoman generator with just enough of a yeoman-environment faked around it, borrowing yeoman-generator and mem-fs from the generator's own install. */
|
|
2
|
+
import type { AnyGenerator, PackEnv } from '@codejen/jen';
|
|
3
|
+
/** A generator package's default generator – what `yo <name>` runs. */
|
|
4
|
+
export declare function shimGenerator(name: string, short: string, env: PackEnv, description: string): AnyGenerator;
|
package/dist/shim.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import { pathToFileURL } from 'node:url';
|
|
11
|
+
import { answerPrompt, argsOf, locate, offline, destination, toActions, toArgv } from "./common.js";
|
|
12
|
+
/** The priorities that run, in order. install/end never do: their side effects assume files already on disk. */
|
|
13
|
+
const PRIORITIES = ['initializing', 'prompting', 'configuring', 'default', 'writing', 'transform', 'conflicts'];
|
|
14
|
+
function makeLog() {
|
|
15
|
+
const log = ((...args) => {
|
|
16
|
+
console.log(...args);
|
|
17
|
+
return log;
|
|
18
|
+
});
|
|
19
|
+
for (const level of ['error', 'warn', 'info', 'ok'])
|
|
20
|
+
log[level] = log;
|
|
21
|
+
log.debug = () => log;
|
|
22
|
+
return log;
|
|
23
|
+
}
|
|
24
|
+
async function run(resolved, namespace, ctx) {
|
|
25
|
+
const args = argsOf(ctx);
|
|
26
|
+
const mod = (await import(__rewriteRelativeImportExtension(pathToFileURL(resolved).href)));
|
|
27
|
+
const GeneratorClass = (mod.default ?? mod);
|
|
28
|
+
if (typeof GeneratorClass !== 'function')
|
|
29
|
+
throw new Error(`${resolved}: the default export is not a Yeoman generator class.`);
|
|
30
|
+
const require = createRequire(resolved);
|
|
31
|
+
const memFs = (await import(__rewriteRelativeImportExtension(pathToFileURL(require.resolve('mem-fs')).href)));
|
|
32
|
+
const sharedFs = memFs.create();
|
|
33
|
+
const queues = new Map(PRIORITIES.map((name) => [name, []]));
|
|
34
|
+
const cwd = destination(ctx);
|
|
35
|
+
const env = {
|
|
36
|
+
cwd,
|
|
37
|
+
logCwd: cwd,
|
|
38
|
+
sharedFs,
|
|
39
|
+
options: {},
|
|
40
|
+
adapter: {
|
|
41
|
+
log: makeLog(),
|
|
42
|
+
prompt: async (questions) => answerPrompt(questions, args.flags),
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function -- only used for progress messages we don't print
|
|
44
|
+
progress: async (fn) => fn({ step() { } }),
|
|
45
|
+
},
|
|
46
|
+
getVersion: () => '999.0.0',
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function -- custom priorities aren't supported, nothing to register
|
|
48
|
+
addPriority() { },
|
|
49
|
+
queueTask(queueName, fn) {
|
|
50
|
+
// install, end and custom priorities are dropped, not run late or folded into another queue
|
|
51
|
+
queues.get(queueName)?.push(fn);
|
|
52
|
+
},
|
|
53
|
+
composeWith() {
|
|
54
|
+
throw new Error('This generator composes other generators (composeWith), which only JHipster gets a real environment for.');
|
|
55
|
+
},
|
|
56
|
+
async runGenerator(generator) {
|
|
57
|
+
await generator.queueTasks();
|
|
58
|
+
for (const name of PRIORITIES) {
|
|
59
|
+
for (const task of queues.get(name) ?? [])
|
|
60
|
+
await task();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function -- nothing listens on the env, so events are dropped
|
|
64
|
+
emit() { },
|
|
65
|
+
};
|
|
66
|
+
const generator = new GeneratorClass(toArgv(args), {
|
|
67
|
+
env,
|
|
68
|
+
resolved,
|
|
69
|
+
namespace,
|
|
70
|
+
skipCache: true,
|
|
71
|
+
skipLocalCache: true,
|
|
72
|
+
skipInstall: true,
|
|
73
|
+
});
|
|
74
|
+
await generator.run();
|
|
75
|
+
return toActions(sharedFs.all(), ctx.root);
|
|
76
|
+
}
|
|
77
|
+
/** A generator package's default generator – what `yo <name>` runs. */
|
|
78
|
+
export function shimGenerator(name, short, env, description) {
|
|
79
|
+
return {
|
|
80
|
+
description,
|
|
81
|
+
passthrough: true,
|
|
82
|
+
actions: async (_answers, _helpers, ctx) => {
|
|
83
|
+
// the package's main, else where yo looks: generators/app, then app
|
|
84
|
+
const resolved = await locate(env, name, [name, `${name}/generators/app`, `${name}/app`]);
|
|
85
|
+
return offline(() => run(resolved, `${short}:app`, ctx));
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codejen/pack-yeoman",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "jen pack that runs Yeoman generators, JHipster included, through jen's plan: never interactive, nothing written before you see it",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/learosema/jen.git",
|
|
14
|
+
"directory": "packages/pack-yeoman"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=24"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"pretypecheck": "npm run build --prefix ../jen",
|
|
24
|
+
"prebuild": "npm run build --prefix ../jen",
|
|
25
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
26
|
+
"typecheck": "tsc",
|
|
27
|
+
"test": "node --test pack-yeoman.test.ts",
|
|
28
|
+
"prepublishOnly": "npm test && npm run typecheck && npm run build"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"jen-pack",
|
|
32
|
+
"yeoman",
|
|
33
|
+
"jhipster"
|
|
34
|
+
],
|
|
35
|
+
"author": "Lea Rosema",
|
|
36
|
+
"license": "ISC",
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@codejen/jen": "^2.0.0 || ^3.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@codejen/jen": "^2.0.0 || ^3.0.0",
|
|
42
|
+
"@types/node": "^26.6.3",
|
|
43
|
+
"mem-fs": "^6.0.0",
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"yeoman-generator": "^10.0.1"
|
|
46
|
+
}
|
|
47
|
+
}
|