@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 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.1 darwin-arm64 node-v24.14.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
- if (originFiles.length > 0) {
110
- const count = originFiles.length;
111
- const noun = count === 1 ? 'file' : 'files';
112
- this.log(`${count} origin ${noun} pending review. Run "catalyst origin list" to inspect or "catalyst origin review" for an interactive walk-through.`);
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 chalk from 'chalk';
4
- import { execFileSync } from 'node:child_process';
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
- let remaining = discoverOrigins();
13
- if (remaining.length === 0) {
8
+ const entries = discoverOrigins();
9
+ if (entries.length === 0) {
14
10
  this.log('No origin files to review.');
15
11
  return;
16
12
  }
17
- while (remaining.length > 0) {
18
- // eslint-disable-next-line no-await-in-loop
19
- const selected = await selectFile(remaining);
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
+ }
@@ -11,7 +11,7 @@ APP_SSR = false
11
11
  APP_FALLBACK_LANG = es
12
12
  # options: id | iso6392 | iso6393 | ietf
13
13
  APP_SEARCH_KEY_LANG = iso6392
14
- APP_CORS_ORIGIN = *
14
+ APP_CORS_ORIGIN =
15
15
 
16
16
  # https://github.com/zeit/ms
17
17
  APP_ACCESS_TOKEN_EXPIRE = 10m
@@ -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
- const displayFieldTemplate = it.crossSchema.getTargetDisplayFieldTemplate(it.property.relationship.modulePath);
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
- const displayFieldTemplate = it.crossSchema.getTargetDisplayFieldTemplate(it.property.relationship.modulePath);
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
- const displayFieldTemplate = it.crossSchema.getTargetDisplayFieldTemplate(it.property.relationship.modulePath);
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
- const displayFieldTemplate = it.crossSchema.getTargetDisplayFieldTemplate(it.property.relationship.modulePath);
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) { -%>
@@ -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.1"
1101
+ "version": "1.0.2"
1084
1102
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aurorajs.dev/catalyst-cli",
3
3
  "description": "A CLI tool to manage Aurora Catalyst projects",
4
- "version": "1.0.1",
4
+ "version": "1.0.2",
5
5
  "author": "José Carlos Rodríguez Palacín",
6
6
  "bin": {
7
7
  "catalyst": "./bin/run.js"