@aurorajs.dev/catalyst-cli 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/generate/index.d.ts +3 -0
- package/dist/commands/generate/index.js +41 -4
- package/dist/commands/origin/review.js +7 -87
- package/dist/origin/interactive/review-mode.d.ts +25 -0
- package/dist/origin/interactive/review-mode.js +22 -0
- package/dist/origin/interactive/review-session.d.ts +36 -0
- package/dist/origin/interactive/review-session.js +123 -0
- package/dist/templates/backend/env/.env +1 -1
- package/dist/templates/codegen/partials/relationships/async-multiple-search-select.eta +8 -3
- package/dist/templates/codegen/partials/relationships/async-search-select.eta +8 -3
- package/dist/templates/codegen/partials/relationships/multiple-search-select.eta +8 -3
- package/dist/templates/codegen/partials/relationships/search-select.eta +8 -3
- package/oclif.manifest.json +19 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ $ npm install -g @aurorajs.dev/catalyst-cli
|
|
|
48
48
|
$ catalyst COMMAND
|
|
49
49
|
running command...
|
|
50
50
|
$ catalyst (--version)
|
|
51
|
-
@aurorajs.dev/catalyst-cli/1.0.
|
|
51
|
+
@aurorajs.dev/catalyst-cli/1.0.2 darwin-arm64 node-v24.14.0
|
|
52
52
|
$ catalyst --help [COMMAND]
|
|
53
53
|
USAGE
|
|
54
54
|
$ catalyst COMMAND
|
|
@@ -9,12 +9,15 @@ export default class Generate extends Command {
|
|
|
9
9
|
static flags: {
|
|
10
10
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'no-review': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
noGraphQLTypes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
overwriteInterface: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
review: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
16
|
target: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
17
|
tests: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
18
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
19
|
};
|
|
18
20
|
run(): Promise<void>;
|
|
21
|
+
private logOriginSummary;
|
|
19
22
|
private parseName;
|
|
20
23
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
3
4
|
import { loadSchema } from '../../generator/domain/schema-loader.js';
|
|
4
5
|
import { loadLockFiles } from '../../generator/engine/lock-file.js';
|
|
5
6
|
import { generateBackModule } from '../../generator/handlers/back.handler.js';
|
|
6
7
|
import { generateFrontModule } from '../../generator/handlers/front.handler.js';
|
|
8
|
+
import { toCurrentPath } from '../../origin/domain/origin-file.js';
|
|
9
|
+
import { resolveReviewMode } from '../../origin/interactive/review-mode.js';
|
|
10
|
+
import { createDefaultReviewSessionIo, runReviewSession, } from '../../origin/interactive/review-session.js';
|
|
7
11
|
export default class Generate extends Command {
|
|
8
12
|
static args = {
|
|
9
13
|
scope: Args.string({
|
|
@@ -36,6 +40,11 @@ export default class Generate extends Command {
|
|
|
36
40
|
description: 'Bounded context and module (e.g. "iam/account").',
|
|
37
41
|
required: true,
|
|
38
42
|
}),
|
|
43
|
+
'no-review': Flags.boolean({
|
|
44
|
+
default: false,
|
|
45
|
+
description: 'Skip the interactive review of generated `.origin` files.',
|
|
46
|
+
exclusive: ['review'],
|
|
47
|
+
}),
|
|
39
48
|
noGraphQLTypes: Flags.boolean({
|
|
40
49
|
char: 'g',
|
|
41
50
|
default: false,
|
|
@@ -46,6 +55,11 @@ export default class Generate extends Command {
|
|
|
46
55
|
default: false,
|
|
47
56
|
description: 'Overwrite front-end interfaces.',
|
|
48
57
|
}),
|
|
58
|
+
review: Flags.boolean({
|
|
59
|
+
default: false,
|
|
60
|
+
description: 'Force the interactive review of generated `.origin` files.',
|
|
61
|
+
exclusive: ['no-review'],
|
|
62
|
+
}),
|
|
49
63
|
target: Flags.string({
|
|
50
64
|
description: 'Output subdirectory (default: "backend" for back, "frontend" for front).',
|
|
51
65
|
}),
|
|
@@ -106,11 +120,34 @@ export default class Generate extends Command {
|
|
|
106
120
|
originFiles = result.originFiles;
|
|
107
121
|
this.log(`Front-end module "${boundedContextName}/${moduleName}" generated.`);
|
|
108
122
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
// Nothing produced → no review, no hint, regardless of mode or TTY.
|
|
124
|
+
if (originFiles.length === 0)
|
|
125
|
+
return;
|
|
126
|
+
const mode = resolveReviewMode({
|
|
127
|
+
isTty: Boolean(process.stdout.isTTY),
|
|
128
|
+
noReview: flags['no-review'],
|
|
129
|
+
review: flags.review,
|
|
130
|
+
});
|
|
131
|
+
if (mode === 'skip') {
|
|
132
|
+
this.logOriginSummary(originFiles.length);
|
|
133
|
+
return;
|
|
113
134
|
}
|
|
135
|
+
// Interactive: walk the user through the files this run produced, then
|
|
136
|
+
// report any they left pending (e.g. they chose Finish early).
|
|
137
|
+
const entries = originFiles.map((origin) => ({
|
|
138
|
+
current: toCurrentPath(origin),
|
|
139
|
+
origin,
|
|
140
|
+
}));
|
|
141
|
+
await runReviewSession(entries, createDefaultReviewSessionIo((message) => {
|
|
142
|
+
this.log(message);
|
|
143
|
+
}));
|
|
144
|
+
const residual = originFiles.filter((origin) => fs.existsSync(origin));
|
|
145
|
+
if (residual.length > 0)
|
|
146
|
+
this.logOriginSummary(residual.length);
|
|
147
|
+
}
|
|
148
|
+
logOriginSummary(count) {
|
|
149
|
+
const noun = count === 1 ? 'file' : 'files';
|
|
150
|
+
this.log(`${count} origin ${noun} pending review. Run "catalyst origin list" to inspect or "catalyst origin review" for an interactive walk-through.`);
|
|
114
151
|
}
|
|
115
152
|
parseName(name) {
|
|
116
153
|
const parts = name.split('/');
|
|
@@ -1,97 +1,17 @@
|
|
|
1
|
-
import select from '@inquirer/select';
|
|
2
1
|
import { Command } from '@oclif/core';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { acceptOrigin, ignoreOrigin, rejectOrigin, } from '../../origin/domain/actions.js';
|
|
7
|
-
import { discoverOrigins, } from '../../origin/domain/origin-file.js';
|
|
2
|
+
import { discoverOrigins } from '../../origin/domain/origin-file.js';
|
|
3
|
+
import { createDefaultReviewSessionIo, runReviewSession, } from '../../origin/interactive/review-session.js';
|
|
8
4
|
export default class OriginReview extends Command {
|
|
9
5
|
static description = 'Interactive walk-through of pending `.origin` files (inquirer + VS Code diff).';
|
|
10
6
|
static examples = ['<%= config.bin %> origin review'];
|
|
11
7
|
async run() {
|
|
12
|
-
|
|
13
|
-
if (
|
|
8
|
+
const entries = discoverOrigins();
|
|
9
|
+
if (entries.length === 0) {
|
|
14
10
|
this.log('No origin files to review.');
|
|
15
11
|
return;
|
|
16
12
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!selected)
|
|
21
|
-
return;
|
|
22
|
-
openDiffInVsCode(selected);
|
|
23
|
-
// eslint-disable-next-line no-await-in-loop
|
|
24
|
-
const action = await selectAction();
|
|
25
|
-
switch (action) {
|
|
26
|
-
case 'accept': {
|
|
27
|
-
const result = acceptOrigin(selected.current);
|
|
28
|
-
this.log(`${chalk.green('[ACCEPTED]')} ${result.path}${result.status === 'noop' ? chalk.dim(' (noop)') : ''}`);
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
case 'finish': {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
case 'go-back': {
|
|
35
|
-
// Loop again to refresh the file picker.
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
case 'ignore': {
|
|
39
|
-
const result = ignoreOrigin(selected.current);
|
|
40
|
-
this.log(`${chalk.cyan('[IGNORED]')} ${result.path}${result.status === 'noop' ? chalk.dim(' (noop)') : ''}`);
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
case 'reject': {
|
|
44
|
-
const result = rejectOrigin(selected.current);
|
|
45
|
-
this.log(`${chalk.yellow('[REJECTED]')} ${result.path}${result.status === 'noop' ? chalk.dim(' (noop)') : ''}`);
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// Refresh the list — the action just removed an entry (or kept it on
|
|
50
|
-
// 'go-back'). Re-scanning is cheap and avoids manual list bookkeeping.
|
|
51
|
-
remaining = discoverOrigins();
|
|
52
|
-
}
|
|
53
|
-
this.log('All origin files processed.');
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async function selectFile(entries) {
|
|
57
|
-
if (entries.length === 0)
|
|
58
|
-
return undefined;
|
|
59
|
-
return select({
|
|
60
|
-
choices: entries.map((entry) => ({ name: entry.current, value: entry })),
|
|
61
|
-
message: 'Select file to review:',
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
async function selectAction() {
|
|
65
|
-
return select({
|
|
66
|
-
choices: [
|
|
67
|
-
{ name: 'Reject (keep my current file as-is)', value: 'reject' },
|
|
68
|
-
{ name: 'Accept (adopt the codegen proposal)', value: 'accept' },
|
|
69
|
-
{
|
|
70
|
-
name: 'Ignore (keep mine + mark file as ignored on future regens)',
|
|
71
|
-
value: 'ignore',
|
|
72
|
-
},
|
|
73
|
-
{ name: 'Go back to file selection', value: 'go-back' },
|
|
74
|
-
{ name: 'Finish review', value: 'finish' },
|
|
75
|
-
],
|
|
76
|
-
message: 'Select an action:',
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
function openDiffInVsCode(entry) {
|
|
80
|
-
const absCurrent = path.resolve(entry.current);
|
|
81
|
-
const absOrigin = path.resolve(entry.origin);
|
|
82
|
-
try {
|
|
83
|
-
// Order matters: `code --diff <left> <right>` puts the first path on the
|
|
84
|
-
// left (source of changes) and the second on the right (editable
|
|
85
|
-
// destination). Reviewing means importing pieces of the codegen proposal
|
|
86
|
-
// (`.origin`) into the user's file, so `.origin` goes left and the
|
|
87
|
-
// current file goes right — that way the inline arrows move chunks from
|
|
88
|
-
// proposal → current file, not the other way around.
|
|
89
|
-
execFileSync('code', ['--diff', absOrigin, absCurrent], {
|
|
90
|
-
stdio: 'ignore',
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// VS Code not on PATH — silently continue. The user can still pick an
|
|
95
|
-
// action without the side-by-side diff.
|
|
13
|
+
await runReviewSession(entries, createDefaultReviewSessionIo((message) => {
|
|
14
|
+
this.log(message);
|
|
15
|
+
}));
|
|
96
16
|
}
|
|
97
17
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure resolution of `catalyst generate`'s end-of-run review mode. Kept free
|
|
3
|
+
* of I/O so the precedence (flags over TTY) is unit-testable without spawning
|
|
4
|
+
* a process or a prompt.
|
|
5
|
+
*/
|
|
6
|
+
export type ReviewMode = 'interactive' | 'skip';
|
|
7
|
+
export interface ReviewModeInputs {
|
|
8
|
+
/** `Boolean(process.stdout.isTTY)` at the call site. */
|
|
9
|
+
isTty: boolean;
|
|
10
|
+
/** The `--no-review` flag. */
|
|
11
|
+
noReview: boolean;
|
|
12
|
+
/** The `--review` flag. */
|
|
13
|
+
review: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Precedence: an explicit flag always wins over TTY detection.
|
|
17
|
+
* - `--review` → interactive
|
|
18
|
+
* - `--no-review` → skip
|
|
19
|
+
* - neither → interactive on a TTY, skip otherwise
|
|
20
|
+
*
|
|
21
|
+
* `--review` and `--no-review` are declared mutually exclusive at the oclif
|
|
22
|
+
* flag layer, so both being true never reaches here; if it ever did,
|
|
23
|
+
* `--review` wins.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveReviewMode({ isTty, noReview, review, }: ReviewModeInputs): ReviewMode;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure resolution of `catalyst generate`'s end-of-run review mode. Kept free
|
|
3
|
+
* of I/O so the precedence (flags over TTY) is unit-testable without spawning
|
|
4
|
+
* a process or a prompt.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Precedence: an explicit flag always wins over TTY detection.
|
|
8
|
+
* - `--review` → interactive
|
|
9
|
+
* - `--no-review` → skip
|
|
10
|
+
* - neither → interactive on a TTY, skip otherwise
|
|
11
|
+
*
|
|
12
|
+
* `--review` and `--no-review` are declared mutually exclusive at the oclif
|
|
13
|
+
* flag layer, so both being true never reaches here; if it ever did,
|
|
14
|
+
* `--review` wins.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveReviewMode({ isTty, noReview, review, }) {
|
|
17
|
+
if (review)
|
|
18
|
+
return 'interactive';
|
|
19
|
+
if (noReview)
|
|
20
|
+
return 'skip';
|
|
21
|
+
return isTty ? 'interactive' : 'skip';
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared interactive `.origin` walk-through. This is the single
|
|
3
|
+
* implementation of the review loop; both `catalyst origin review` and
|
|
4
|
+
* `catalyst generate`'s interactive mode delegate to `runReviewSession`.
|
|
5
|
+
*
|
|
6
|
+
* Action logic lives in the domain (`acceptOrigin`/`rejectOrigin`/
|
|
7
|
+
* `ignoreOrigin`). This module owns only the orchestration plus the I/O
|
|
8
|
+
* concerns (prompts, the `code --diff` viewer, log formatting), which are
|
|
9
|
+
* injectable via `ReviewSessionIo` so the loop is testable without driving
|
|
10
|
+
* real prompts. These I/O concerns MUST NOT bleed into the domain.
|
|
11
|
+
*/
|
|
12
|
+
import { type OriginEntry } from '../domain/origin-file.js';
|
|
13
|
+
export type ReviewAction = 'accept' | 'finish' | 'go-back' | 'ignore' | 'reject';
|
|
14
|
+
/**
|
|
15
|
+
* The injectable I/O seam. The default implementation uses `@inquirer/select`
|
|
16
|
+
* for prompts and `code --diff` for the diff view; tests inject a stub.
|
|
17
|
+
*/
|
|
18
|
+
export interface ReviewSessionIo {
|
|
19
|
+
log(message: string): void;
|
|
20
|
+
openDiff(entry: OriginEntry): void;
|
|
21
|
+
selectAction(): Promise<ReviewAction>;
|
|
22
|
+
selectFile(entries: OriginEntry[]): Promise<OriginEntry | undefined>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Walk the user through the given `.origin` entries. The entry set is scoped
|
|
26
|
+
* by the caller: `origin review` passes the full cwd discovery, `generate`
|
|
27
|
+
* passes only the files its run produced. "Remaining" is refreshed by
|
|
28
|
+
* checking which `.origin` files still exist on disk after each action — an
|
|
29
|
+
* entry is done when its `.origin` is gone — so the loop never re-scans cwd.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runReviewSession(entries: OriginEntry[], io?: ReviewSessionIo): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Default I/O: inquirer prompts + VS Code diff. `log` defaults to stdout but
|
|
34
|
+
* callers (oclif commands) inject `this.log` to route through oclif output.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createDefaultReviewSessionIo(log?: (message: string) => void): ReviewSessionIo;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared interactive `.origin` walk-through. This is the single
|
|
3
|
+
* implementation of the review loop; both `catalyst origin review` and
|
|
4
|
+
* `catalyst generate`'s interactive mode delegate to `runReviewSession`.
|
|
5
|
+
*
|
|
6
|
+
* Action logic lives in the domain (`acceptOrigin`/`rejectOrigin`/
|
|
7
|
+
* `ignoreOrigin`). This module owns only the orchestration plus the I/O
|
|
8
|
+
* concerns (prompts, the `code --diff` viewer, log formatting), which are
|
|
9
|
+
* injectable via `ReviewSessionIo` so the loop is testable without driving
|
|
10
|
+
* real prompts. These I/O concerns MUST NOT bleed into the domain.
|
|
11
|
+
*/
|
|
12
|
+
import select from '@inquirer/select';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { acceptOrigin, ignoreOrigin, rejectOrigin, } from '../domain/actions.js';
|
|
18
|
+
/**
|
|
19
|
+
* Walk the user through the given `.origin` entries. The entry set is scoped
|
|
20
|
+
* by the caller: `origin review` passes the full cwd discovery, `generate`
|
|
21
|
+
* passes only the files its run produced. "Remaining" is refreshed by
|
|
22
|
+
* checking which `.origin` files still exist on disk after each action — an
|
|
23
|
+
* entry is done when its `.origin` is gone — so the loop never re-scans cwd.
|
|
24
|
+
*/
|
|
25
|
+
export async function runReviewSession(entries, io = createDefaultReviewSessionIo()) {
|
|
26
|
+
let remaining = entries.filter((entry) => stillPending(entry));
|
|
27
|
+
if (remaining.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
while (remaining.length > 0) {
|
|
30
|
+
// eslint-disable-next-line no-await-in-loop
|
|
31
|
+
const selected = await io.selectFile(remaining);
|
|
32
|
+
if (!selected)
|
|
33
|
+
return;
|
|
34
|
+
io.openDiff(selected);
|
|
35
|
+
// eslint-disable-next-line no-await-in-loop
|
|
36
|
+
const action = await io.selectAction();
|
|
37
|
+
if (action === 'finish')
|
|
38
|
+
return;
|
|
39
|
+
if (action !== 'go-back') {
|
|
40
|
+
io.log(applyAction(action, selected));
|
|
41
|
+
}
|
|
42
|
+
// Re-derive from the scoped set: an entry is done once its `.origin` is
|
|
43
|
+
// gone (accept renames it away; reject/ignore delete it). 'go-back' keeps
|
|
44
|
+
// the entry, so the list is unchanged on that branch.
|
|
45
|
+
remaining = remaining.filter((entry) => stillPending(entry));
|
|
46
|
+
}
|
|
47
|
+
io.log('All origin files processed.');
|
|
48
|
+
}
|
|
49
|
+
function applyAction(action, entry) {
|
|
50
|
+
switch (action) {
|
|
51
|
+
case 'accept': {
|
|
52
|
+
const result = acceptOrigin(entry.current);
|
|
53
|
+
return `${chalk.green('[ACCEPTED]')} ${result.path}${result.status === 'noop' ? chalk.dim(' (noop)') : ''}`;
|
|
54
|
+
}
|
|
55
|
+
case 'ignore': {
|
|
56
|
+
const result = ignoreOrigin(entry.current);
|
|
57
|
+
return `${chalk.cyan('[IGNORED]')} ${result.path}${result.status === 'noop' ? chalk.dim(' (noop)') : ''}`;
|
|
58
|
+
}
|
|
59
|
+
case 'reject': {
|
|
60
|
+
const result = rejectOrigin(entry.current);
|
|
61
|
+
return `${chalk.yellow('[REJECTED]')} ${result.path}${result.status === 'noop' ? chalk.dim(' (noop)') : ''}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function stillPending(entry) {
|
|
66
|
+
return fs.existsSync(entry.origin);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Default I/O: inquirer prompts + VS Code diff. `log` defaults to stdout but
|
|
70
|
+
* callers (oclif commands) inject `this.log` to route through oclif output.
|
|
71
|
+
*/
|
|
72
|
+
export function createDefaultReviewSessionIo(log = (message) => {
|
|
73
|
+
console.log(message);
|
|
74
|
+
}) {
|
|
75
|
+
return {
|
|
76
|
+
log,
|
|
77
|
+
openDiff: openDiffInVsCode,
|
|
78
|
+
selectAction,
|
|
79
|
+
selectFile,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function selectFile(entries) {
|
|
83
|
+
if (entries.length === 0)
|
|
84
|
+
return undefined;
|
|
85
|
+
return select({
|
|
86
|
+
choices: entries.map((entry) => ({ name: entry.current, value: entry })),
|
|
87
|
+
message: 'Select file to review:',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function selectAction() {
|
|
91
|
+
return select({
|
|
92
|
+
choices: [
|
|
93
|
+
{ name: 'Reject (keep my current file as-is)', value: 'reject' },
|
|
94
|
+
{ name: 'Accept (adopt the codegen proposal)', value: 'accept' },
|
|
95
|
+
{
|
|
96
|
+
name: 'Ignore (keep mine + mark file as ignored on future regens)',
|
|
97
|
+
value: 'ignore',
|
|
98
|
+
},
|
|
99
|
+
{ name: 'Go back to file selection', value: 'go-back' },
|
|
100
|
+
{ name: 'Finish review', value: 'finish' },
|
|
101
|
+
],
|
|
102
|
+
message: 'Select an action:',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function openDiffInVsCode(entry) {
|
|
106
|
+
const absCurrent = path.resolve(entry.current);
|
|
107
|
+
const absOrigin = path.resolve(entry.origin);
|
|
108
|
+
try {
|
|
109
|
+
// Order matters: `code --diff <left> <right>` puts the first path on the
|
|
110
|
+
// left (source of changes) and the second on the right (editable
|
|
111
|
+
// destination). Reviewing means importing pieces of the codegen proposal
|
|
112
|
+
// (`.origin`) into the user's file, so `.origin` goes left and the
|
|
113
|
+
// current file goes right — that way the inline arrows move chunks from
|
|
114
|
+
// proposal → current file, not the other way around.
|
|
115
|
+
execFileSync('code', ['--diff', absOrigin, absCurrent], {
|
|
116
|
+
stdio: 'ignore',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// VS Code not on PATH — silently continue. The user can still pick an
|
|
121
|
+
// action without the side-by-side diff.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -13,8 +13,13 @@
|
|
|
13
13
|
// [inputId] binding ties <label for=...> to the internal Spartan
|
|
14
14
|
// <hlm-combobox-input id=...>.
|
|
15
15
|
const relSingular = it.fmt.toCamelCase(it.mappers.getRelationshipSingularName(it.property));
|
|
16
|
-
const optionsName = relSingular + 'Options';
|
|
17
|
-
|
|
16
|
+
const optionsName = it.property.relationship?.modulePath ? relSingular + 'Options' : it.propCamel;
|
|
17
|
+
// A relational widget can land on a property WITHOUT a relationship (e.g. a
|
|
18
|
+
// scalar `array<varchar>`). Only resolve the target displayField when there
|
|
19
|
+
// is a relationship; otherwise render without the pipe (pre-regression shape).
|
|
20
|
+
const displayFieldTemplate = it.property.relationship?.modulePath
|
|
21
|
+
? it.crossSchema.getTargetDisplayFieldTemplate(it.property.relationship.modulePath)
|
|
22
|
+
: null;
|
|
18
23
|
const isRequired = it.property.nullable === false && it.property.type !== 'boolean';
|
|
19
24
|
_%>
|
|
20
25
|
<hlm-field class="<%= it.colSpan %>">
|
|
@@ -22,7 +27,7 @@ _%>
|
|
|
22
27
|
<au-async-multiple-search-select
|
|
23
28
|
[inputId]="'<%= it.propCamel %>'"
|
|
24
29
|
[value]="signalForm.controls['<%= it.propCamel %>'].value ?? []"
|
|
25
|
-
[options]="<%= optionsName %>() | toOptions:'<%= displayFieldTemplate %>'"
|
|
30
|
+
[options]="<%= optionsName %>()<% if (displayFieldTemplate) { %> | toOptions:'<%= displayFieldTemplate %>'<% } %>"
|
|
26
31
|
[loading]="<%= relSingular %>Loading()"
|
|
27
32
|
[placeholder]="t('Select')"
|
|
28
33
|
[emptyMessage]="t('NoResults')"
|
|
@@ -14,8 +14,13 @@
|
|
|
14
14
|
// [inputId] binding ties <label for=...> to the internal Spartan
|
|
15
15
|
// <hlm-combobox-input id=...>.
|
|
16
16
|
const relSingular = it.fmt.toCamelCase(it.mappers.getRelationshipSingularName(it.property));
|
|
17
|
-
const optionsName = relSingular + 'Options';
|
|
18
|
-
|
|
17
|
+
const optionsName = it.property.relationship?.modulePath ? relSingular + 'Options' : it.propCamel;
|
|
18
|
+
// A relational widget can land on a property WITHOUT a relationship (e.g. a
|
|
19
|
+
// scalar `array<varchar>`). Only resolve the target displayField when there
|
|
20
|
+
// is a relationship; otherwise render without the pipe (pre-regression shape).
|
|
21
|
+
const displayFieldTemplate = it.property.relationship?.modulePath
|
|
22
|
+
? it.crossSchema.getTargetDisplayFieldTemplate(it.property.relationship.modulePath)
|
|
23
|
+
: null;
|
|
19
24
|
const isRequired = it.property.nullable === false && it.property.type !== 'boolean';
|
|
20
25
|
_%>
|
|
21
26
|
<hlm-field class="<%= it.colSpan %>">
|
|
@@ -23,7 +28,7 @@ _%>
|
|
|
23
28
|
<au-async-search-select
|
|
24
29
|
[inputId]="'<%= it.propCamel %>'"
|
|
25
30
|
[value]="signalForm.controls['<%= it.propCamel %>'].value"
|
|
26
|
-
[options]="<%= optionsName %>() | toOptions:'<%= displayFieldTemplate %>'"
|
|
31
|
+
[options]="<%= optionsName %>()<% if (displayFieldTemplate) { %> | toOptions:'<%= displayFieldTemplate %>'<% } %>"
|
|
27
32
|
[loading]="<%= relSingular %>Loading()"
|
|
28
33
|
[placeholder]="t('Select')"
|
|
29
34
|
[emptyMessage]="t('NoResults')"
|
|
@@ -11,8 +11,13 @@
|
|
|
11
11
|
// [inputId] binding ties <label for=...> to the internal Spartan
|
|
12
12
|
// <hlm-combobox-input id=...>.
|
|
13
13
|
const relSingular = it.fmt.toCamelCase(it.mappers.getRelationshipSingularName(it.property));
|
|
14
|
-
const optionsName = relSingular + 'Options';
|
|
15
|
-
|
|
14
|
+
const optionsName = it.property.relationship?.modulePath ? relSingular + 'Options' : it.propCamel;
|
|
15
|
+
// A relational widget can land on a property WITHOUT a relationship (e.g. a
|
|
16
|
+
// scalar `array<varchar>`). Only resolve the target displayField when there
|
|
17
|
+
// is a relationship; otherwise render without the pipe (pre-regression shape).
|
|
18
|
+
const displayFieldTemplate = it.property.relationship?.modulePath
|
|
19
|
+
? it.crossSchema.getTargetDisplayFieldTemplate(it.property.relationship.modulePath)
|
|
20
|
+
: null;
|
|
16
21
|
const isRequired = it.property.nullable === false && it.property.type !== 'boolean';
|
|
17
22
|
_%>
|
|
18
23
|
<hlm-field class="<%= it.colSpan %>">
|
|
@@ -20,7 +25,7 @@ _%>
|
|
|
20
25
|
<au-multiple-search-select
|
|
21
26
|
[inputId]="'<%= it.propCamel %>'"
|
|
22
27
|
[value]="signalForm.controls['<%= it.propCamel %>'].value ?? []"
|
|
23
|
-
[options]="<%= optionsName %>() | toOptions:'<%= displayFieldTemplate %>'"
|
|
28
|
+
[options]="<%= optionsName %>()<% if (displayFieldTemplate) { %> | toOptions:'<%= displayFieldTemplate %>'<% } %>"
|
|
24
29
|
[placeholder]="t('Select')"
|
|
25
30
|
[emptyMessage]="t('NoResults')"
|
|
26
31
|
<% if (isRequired) { -%>
|
|
@@ -17,8 +17,13 @@
|
|
|
17
17
|
// input (depends on the companion `aurora-catalyst` PR exposing `inputId`
|
|
18
18
|
// on the wrapper).
|
|
19
19
|
const relSingular = it.fmt.toCamelCase(it.mappers.getRelationshipSingularName(it.property));
|
|
20
|
-
const optionsName = relSingular + 'Options';
|
|
21
|
-
|
|
20
|
+
const optionsName = it.property.relationship?.modulePath ? relSingular + 'Options' : it.propCamel;
|
|
21
|
+
// A relational widget can land on a property WITHOUT a relationship (e.g. a
|
|
22
|
+
// scalar `array<varchar>`). Only resolve the target displayField when there
|
|
23
|
+
// is a relationship; otherwise render without the pipe (pre-regression shape).
|
|
24
|
+
const displayFieldTemplate = it.property.relationship?.modulePath
|
|
25
|
+
? it.crossSchema.getTargetDisplayFieldTemplate(it.property.relationship.modulePath)
|
|
26
|
+
: null;
|
|
22
27
|
const isRequired = it.property.nullable === false && it.property.type !== 'boolean';
|
|
23
28
|
_%>
|
|
24
29
|
<hlm-field class="<%= it.colSpan %>">
|
|
@@ -26,7 +31,7 @@ _%>
|
|
|
26
31
|
<au-search-select
|
|
27
32
|
[inputId]="'<%= it.propCamel %>'"
|
|
28
33
|
[value]="signalForm.controls['<%= it.propCamel %>'].value"
|
|
29
|
-
[options]="<%= optionsName %>() | toOptions:'<%= displayFieldTemplate %>'"
|
|
34
|
+
[options]="<%= optionsName %>()<% if (displayFieldTemplate) { %> | toOptions:'<%= displayFieldTemplate %>'<% } %>"
|
|
30
35
|
[placeholder]="t('Select')"
|
|
31
36
|
[emptyMessage]="t('NoResults')"
|
|
32
37
|
<% if (isRequired) { -%>
|
package/oclif.manifest.json
CHANGED
|
@@ -322,6 +322,15 @@
|
|
|
322
322
|
"multiple": false,
|
|
323
323
|
"type": "option"
|
|
324
324
|
},
|
|
325
|
+
"no-review": {
|
|
326
|
+
"description": "Skip the interactive review of generated `.origin` files.",
|
|
327
|
+
"exclusive": [
|
|
328
|
+
"review"
|
|
329
|
+
],
|
|
330
|
+
"name": "no-review",
|
|
331
|
+
"allowNo": false,
|
|
332
|
+
"type": "boolean"
|
|
333
|
+
},
|
|
325
334
|
"noGraphQLTypes": {
|
|
326
335
|
"char": "g",
|
|
327
336
|
"description": "Skip GraphQL type generation.",
|
|
@@ -336,6 +345,15 @@
|
|
|
336
345
|
"allowNo": false,
|
|
337
346
|
"type": "boolean"
|
|
338
347
|
},
|
|
348
|
+
"review": {
|
|
349
|
+
"description": "Force the interactive review of generated `.origin` files.",
|
|
350
|
+
"exclusive": [
|
|
351
|
+
"no-review"
|
|
352
|
+
],
|
|
353
|
+
"name": "review",
|
|
354
|
+
"allowNo": false,
|
|
355
|
+
"type": "boolean"
|
|
356
|
+
},
|
|
339
357
|
"target": {
|
|
340
358
|
"description": "Output subdirectory (default: \"backend\" for back, \"frontend\" for front).",
|
|
341
359
|
"name": "target",
|
|
@@ -1080,5 +1098,5 @@
|
|
|
1080
1098
|
]
|
|
1081
1099
|
}
|
|
1082
1100
|
},
|
|
1083
|
-
"version": "1.0.
|
|
1101
|
+
"version": "1.0.2"
|
|
1084
1102
|
}
|
package/package.json
CHANGED