@cedarjs/cli 6.0.0-canary.2919 → 6.0.0-canary.2923
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/dist/commands/generate/cell/cell.js +5 -0
- package/dist/commands/generate/cell/cellHandler.js +61 -19
- package/dist/commands/generate/cell/templates/cellFragment.tsx.template +43 -0
- package/dist/commands/generate/cell/templates/storiesFragment.tsx.template +23 -0
- package/dist/commands/generate/cell/templates/testFragment.js.template +30 -0
- package/dist/commands/generate/scaffold/scaffoldHandler.js +54 -13
- package/package.json +14 -14
|
@@ -37,6 +37,11 @@ const builder = createBuilder({
|
|
|
37
37
|
default: false,
|
|
38
38
|
description: "Include a typed `isEmpty` stub for overriding the default check for the Empty component.",
|
|
39
39
|
type: "boolean"
|
|
40
|
+
},
|
|
41
|
+
fragment: {
|
|
42
|
+
default: "",
|
|
43
|
+
description: "Generate a fragment cell (exports FRAGMENT instead of QUERY) that reads its data from a parent Cell, given the GraphQL type it selects from - e.g. --fragment User.",
|
|
44
|
+
type: "string"
|
|
40
45
|
}
|
|
41
46
|
};
|
|
42
47
|
}
|
|
@@ -29,7 +29,8 @@ const files = async ({
|
|
|
29
29
|
tests,
|
|
30
30
|
beforeQuery = false,
|
|
31
31
|
afterQuery = false,
|
|
32
|
-
isEmpty = false
|
|
32
|
+
isEmpty = false,
|
|
33
|
+
fragment = ""
|
|
33
34
|
}) => {
|
|
34
35
|
let cellName = removeGeneratorName(name, "cell");
|
|
35
36
|
let idName = "id";
|
|
@@ -38,22 +39,49 @@ const files = async ({
|
|
|
38
39
|
let model = null;
|
|
39
40
|
let templateNameSuffix = "";
|
|
40
41
|
let typeName = cellName;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
if (fragment) {
|
|
43
|
+
if (list) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"The --list flag cannot be combined with --fragment; fragment cells always render a single item."
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (beforeQuery) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"The --before-query flag cannot be combined with --fragment; fragment cells never fire a query of their own."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (query) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"The --query flag cannot be combined with --fragment; fragment cells don't have an operation name."
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const shouldGenerateList = !fragment && ((isWordPluralizable(cellName) ? isPlural(cellName) : list) || list);
|
|
60
|
+
if (!fragment) {
|
|
61
|
+
try {
|
|
62
|
+
model = await getSchema(pascalcase(singularize(cellName)));
|
|
63
|
+
idName = getIdName(model);
|
|
64
|
+
idType = getIdType(model);
|
|
65
|
+
typeName = model.name;
|
|
66
|
+
mockIdValues = idType === "String" ? mockIdValues.map((value) => `'${value}'`) : mockIdValues;
|
|
67
|
+
} catch {
|
|
68
|
+
idType = "Int";
|
|
69
|
+
}
|
|
50
70
|
}
|
|
51
71
|
if (shouldGenerateList) {
|
|
52
72
|
cellName = forcePluralizeWord(cellName);
|
|
53
73
|
templateNameSuffix = "List";
|
|
54
74
|
}
|
|
55
75
|
let operationName = query;
|
|
56
|
-
|
|
76
|
+
let fragmentName;
|
|
77
|
+
let fragmentOnType;
|
|
78
|
+
let fragmentPropName;
|
|
79
|
+
if (fragment) {
|
|
80
|
+
const fragmentTypeVariants = nameVariants(fragment);
|
|
81
|
+
fragmentOnType = fragmentTypeVariants.pascalName;
|
|
82
|
+
fragmentPropName = fragmentTypeVariants.camelName;
|
|
83
|
+
fragmentName = `${nameVariants(cellName).pascalName}Cell_${fragmentPropName}`;
|
|
84
|
+
} else if (operationName) {
|
|
57
85
|
const userSpecifiedOperationNameIsUnique = await operationNameIsUnique(operationName);
|
|
58
86
|
if (!userSpecifiedOperationNameIsUnique) {
|
|
59
87
|
throw new Error(`Specified query name: "${operationName}" is not unique!`);
|
|
@@ -70,8 +98,14 @@ const files = async ({
|
|
|
70
98
|
extension,
|
|
71
99
|
webPathSection: CEDAR_WEB_PATH_NAME,
|
|
72
100
|
generator: "cell",
|
|
73
|
-
templatePath: `cell${templateNameSuffix}.tsx.template`,
|
|
74
|
-
templateVars: {
|
|
101
|
+
templatePath: fragment ? "cellFragment.tsx.template" : `cell${templateNameSuffix}.tsx.template`,
|
|
102
|
+
templateVars: fragment ? {
|
|
103
|
+
fragmentName,
|
|
104
|
+
fragmentOnType,
|
|
105
|
+
camelName: fragmentPropName,
|
|
106
|
+
afterQuery,
|
|
107
|
+
isEmpty
|
|
108
|
+
} : {
|
|
75
109
|
operationName,
|
|
76
110
|
idName,
|
|
77
111
|
idType,
|
|
@@ -86,8 +120,8 @@ const files = async ({
|
|
|
86
120
|
extension: `.test${extension}`,
|
|
87
121
|
webPathSection: CEDAR_WEB_PATH_NAME,
|
|
88
122
|
generator: "cell",
|
|
89
|
-
templatePath: "test.js.template",
|
|
90
|
-
templateVars: {
|
|
123
|
+
templatePath: fragment ? "testFragment.js.template" : "test.js.template",
|
|
124
|
+
templateVars: fragment ? { camelName: fragmentPropName } : {
|
|
91
125
|
idName: shouldGenerateList ? void 0 : idName,
|
|
92
126
|
mockIdValues: shouldGenerateList ? void 0 : mockIdValues
|
|
93
127
|
}
|
|
@@ -98,7 +132,7 @@ const files = async ({
|
|
|
98
132
|
extension: `.stories${extension}`,
|
|
99
133
|
webPathSection: CEDAR_WEB_PATH_NAME,
|
|
100
134
|
generator: "cell",
|
|
101
|
-
templatePath: "stories.tsx.template"
|
|
135
|
+
templatePath: fragment ? "storiesFragment.tsx.template" : "stories.tsx.template"
|
|
102
136
|
});
|
|
103
137
|
const mockFile = await templateForComponentFile({
|
|
104
138
|
name: cellName,
|
|
@@ -107,7 +141,12 @@ const files = async ({
|
|
|
107
141
|
webPathSection: CEDAR_WEB_PATH_NAME,
|
|
108
142
|
generator: "cell",
|
|
109
143
|
templatePath: `mock${templateNameSuffix}.ts.template`,
|
|
110
|
-
templateVars: {
|
|
144
|
+
templateVars: fragment ? {
|
|
145
|
+
idName,
|
|
146
|
+
mockIdValues,
|
|
147
|
+
typeName: fragmentOnType,
|
|
148
|
+
camelName: fragmentPropName
|
|
149
|
+
} : {
|
|
111
150
|
idName,
|
|
112
151
|
mockIdValues,
|
|
113
152
|
typeName
|
|
@@ -128,7 +167,10 @@ const files = async ({
|
|
|
128
167
|
const handler = createHandler({
|
|
129
168
|
componentName: "cell",
|
|
130
169
|
filesFn: files,
|
|
131
|
-
includeAdditionalTasks: ({
|
|
170
|
+
includeAdditionalTasks: ({
|
|
171
|
+
name: cellName,
|
|
172
|
+
fragment
|
|
173
|
+
}) => {
|
|
132
174
|
return [
|
|
133
175
|
{
|
|
134
176
|
title: `Generating types ...`,
|
|
@@ -136,7 +178,7 @@ const handler = createHandler({
|
|
|
136
178
|
const queryFieldName = nameVariants(
|
|
137
179
|
removeGeneratorName(cellName, "cell")
|
|
138
180
|
).camelName;
|
|
139
|
-
const projectHasSdl = await checkProjectForQueryField(queryFieldName);
|
|
181
|
+
const projectHasSdl = Boolean(fragment) || await checkProjectForQueryField(queryFieldName);
|
|
140
182
|
if (projectHasSdl) {
|
|
141
183
|
const { errors } = await generateTypes();
|
|
142
184
|
for (const { message, error } of errors) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ${fragmentName} } from 'types/graphql'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
CellSuccessProps,<% if (afterQuery || isEmpty) { %>
|
|
5
|
+
DataObject,<% } %>
|
|
6
|
+
} from '@cedarjs/web'
|
|
7
|
+
|
|
8
|
+
// A parent Cell spreads this fragment by name in its own QUERY, and passes
|
|
9
|
+
// the matching field down as the `${camelName}` prop - no import needed,
|
|
10
|
+
// just render <${pascalName}Cell />:
|
|
11
|
+
//
|
|
12
|
+
// query FindSomething($id: Int!) {
|
|
13
|
+
// something(id: $id) {
|
|
14
|
+
// id
|
|
15
|
+
// ...${fragmentName}
|
|
16
|
+
// }
|
|
17
|
+
// }
|
|
18
|
+
//
|
|
19
|
+
// <${pascalName}Cell ${camelName}={something} />
|
|
20
|
+
export const FRAGMENT = gql`
|
|
21
|
+
fragment ${fragmentName} on ${fragmentOnType} {
|
|
22
|
+
id
|
|
23
|
+
}
|
|
24
|
+
`
|
|
25
|
+
<% if (isEmpty) { %>
|
|
26
|
+
export const isEmpty = (
|
|
27
|
+
data: DataObject,
|
|
28
|
+
{ isDataEmpty }: { isDataEmpty: (data: DataObject) => boolean },
|
|
29
|
+
) => {
|
|
30
|
+
return isDataEmpty(data)
|
|
31
|
+
}
|
|
32
|
+
<% } %><% if (afterQuery) { %>
|
|
33
|
+
export const afterQuery = (data: DataObject): DataObject => {
|
|
34
|
+
return data
|
|
35
|
+
}
|
|
36
|
+
<% } %>
|
|
37
|
+
export const Empty = () => <div>Empty</div>
|
|
38
|
+
|
|
39
|
+
export const Success = ({
|
|
40
|
+
${camelName},
|
|
41
|
+
}: CellSuccessProps<{ ${camelName}: ${fragmentName} }>) => {
|
|
42
|
+
return <div>{JSON.stringify(${camelName})}</div>
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
import { Empty, Success } from './${pascalName}Cell'
|
|
4
|
+
import { standard } from './${pascalName}Cell.mock'
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: 'Cells/${pascalName}Cell',
|
|
8
|
+
tags: ['autodocs']
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
|
|
13
|
+
export const empty: StoryObj<typeof Empty> = {
|
|
14
|
+
render: () => {
|
|
15
|
+
return Empty ? <Empty /> : <></>
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const success: StoryObj<typeof Success> = {
|
|
20
|
+
render: (args) => {
|
|
21
|
+
return Success ? <Success {...standard()} {...args} /> : <></>
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { render } from '@cedarjs/testing/web'
|
|
2
|
+
|
|
3
|
+
import { Empty, Success } from './${pascalName}Cell'
|
|
4
|
+
import { standard } from './${pascalName}Cell.mock'
|
|
5
|
+
|
|
6
|
+
// Generated boilerplate tests do not account for all circumstances
|
|
7
|
+
// and can fail without adjustments, e.g. Float and DateTime types.
|
|
8
|
+
// Please refer to the RedwoodJS Testing Docs:
|
|
9
|
+
// https://cedarjs.com/docs/testing#testing-cells
|
|
10
|
+
// https://cedarjs.com/docs/testing#jest-expect-type-considerations
|
|
11
|
+
|
|
12
|
+
describe('${pascalName}Cell', () => {
|
|
13
|
+
it('renders Empty successfully', async () => {
|
|
14
|
+
expect(() => {
|
|
15
|
+
render(<Empty />)
|
|
16
|
+
}).not.toThrow()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// When you're ready to test the actual output of your component render
|
|
20
|
+
// you could test that, for example, certain text is present:
|
|
21
|
+
//
|
|
22
|
+
// 1. import { screen } from '@cedarjs/testing/web'
|
|
23
|
+
// 2. Add test: expect(screen.getByText('Hello, world')).toBeInTheDocument()
|
|
24
|
+
|
|
25
|
+
it('renders Success successfully', async () => {
|
|
26
|
+
expect(() => {
|
|
27
|
+
render(<Success ${camelName}={standard().${camelName}} />)
|
|
28
|
+
}).not.toThrow()
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -6,6 +6,7 @@ import humanize from "humanize-string";
|
|
|
6
6
|
import { Listr } from "listr2";
|
|
7
7
|
import pascalcase from "pascalcase";
|
|
8
8
|
import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
|
|
9
|
+
import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
|
|
9
10
|
import {
|
|
10
11
|
addWorkspacePackages,
|
|
11
12
|
removeWorkspacePackages
|
|
@@ -38,10 +39,14 @@ import {
|
|
|
38
39
|
import { builder as sdlBuilder } from "../sdl/sdl.js";
|
|
39
40
|
import {
|
|
40
41
|
files as sdlFiles,
|
|
42
|
+
stubFiles as sdlStubFiles,
|
|
41
43
|
printRedactedFieldsNote,
|
|
42
44
|
redactedSensitiveFields
|
|
43
45
|
} from "../sdl/sdlHandler.js";
|
|
44
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
missingRelatedModels,
|
|
48
|
+
writeFilesWithStubsTask
|
|
49
|
+
} from "../sdl/stubFiles.js";
|
|
45
50
|
import { builder as serviceBuilder } from "../service/service.js";
|
|
46
51
|
import { files as serviceFiles } from "../service/serviceHandler.js";
|
|
47
52
|
import { customOrDefaultTemplatePath } from "../yargsHandlerHelpers.js";
|
|
@@ -630,22 +635,26 @@ const tasks = ({
|
|
|
630
635
|
tests,
|
|
631
636
|
typescript,
|
|
632
637
|
javascript: _javascript,
|
|
633
|
-
tailwind
|
|
638
|
+
tailwind,
|
|
639
|
+
missingModels
|
|
634
640
|
}) => {
|
|
635
641
|
return new Listr(
|
|
636
642
|
[
|
|
637
643
|
{
|
|
638
644
|
title: "Generating scaffold files...",
|
|
639
645
|
task: async () => {
|
|
640
|
-
const f =
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
646
|
+
const f = {
|
|
647
|
+
...await files({
|
|
648
|
+
docs,
|
|
649
|
+
model,
|
|
650
|
+
path: path2,
|
|
651
|
+
tests,
|
|
652
|
+
typescript,
|
|
653
|
+
tailwind,
|
|
654
|
+
force
|
|
655
|
+
}),
|
|
656
|
+
...await sdlStubFiles(missingModels, model, { docs, typescript })
|
|
657
|
+
};
|
|
649
658
|
return writeFilesWithStubsTask(f, { overwriteExisting: force });
|
|
650
659
|
}
|
|
651
660
|
},
|
|
@@ -711,6 +720,7 @@ const handler = async ({
|
|
|
711
720
|
tailwind = shouldUseTailwindCSS(tailwind);
|
|
712
721
|
try {
|
|
713
722
|
const { name } = await verifyModelName({ name: model });
|
|
723
|
+
const missingModels = await missingRelatedModels(name);
|
|
714
724
|
const t = tasks({
|
|
715
725
|
docs,
|
|
716
726
|
model: name,
|
|
@@ -718,13 +728,44 @@ const handler = async ({
|
|
|
718
728
|
force,
|
|
719
729
|
tests,
|
|
720
730
|
typescript,
|
|
721
|
-
tailwind
|
|
731
|
+
tailwind,
|
|
732
|
+
missingModels
|
|
722
733
|
});
|
|
723
734
|
if (rollback && !force) {
|
|
724
735
|
prepareForRollback(t);
|
|
725
736
|
}
|
|
726
737
|
await t.run();
|
|
727
|
-
|
|
738
|
+
if (missingModels.length > 0) {
|
|
739
|
+
console.log();
|
|
740
|
+
console.log(
|
|
741
|
+
c.info(
|
|
742
|
+
`${name} has relations to models that don't have SDL files of their own yet: ${missingModels.join(", ")}`
|
|
743
|
+
)
|
|
744
|
+
);
|
|
745
|
+
console.log(
|
|
746
|
+
c.info(
|
|
747
|
+
"Read-only SDL stubs were generated for them, since GraphQL type generation fails otherwise."
|
|
748
|
+
)
|
|
749
|
+
);
|
|
750
|
+
console.log(
|
|
751
|
+
c.info("To replace a stub, run one of the following for each model:")
|
|
752
|
+
);
|
|
753
|
+
for (const stubModel of missingModels) {
|
|
754
|
+
console.log(
|
|
755
|
+
c.info(
|
|
756
|
+
` ${formatCedarCommand(["generate", "sdl", stubModel])} (SDL + service only)`
|
|
757
|
+
)
|
|
758
|
+
);
|
|
759
|
+
console.log(
|
|
760
|
+
c.info(
|
|
761
|
+
` ${formatCedarCommand(["generate", "scaffold", stubModel])} (adds pages, cells, and forms too)`
|
|
762
|
+
)
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
printRedactedFieldsNote(
|
|
767
|
+
await redactedSensitiveFields([name, ...missingModels])
|
|
768
|
+
);
|
|
728
769
|
} catch (e) {
|
|
729
770
|
const message = e instanceof Error ? e.message : String(e);
|
|
730
771
|
const exitCode = e instanceof Error && "exitCode" in e ? e.exitCode ?? 1 : 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/cli",
|
|
3
|
-
"version": "6.0.0-canary.
|
|
3
|
+
"version": "6.0.0-canary.2923",
|
|
4
4
|
"description": "The CedarJS Command Line",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,17 +36,17 @@
|
|
|
36
36
|
"@babel/preset-typescript": "7.29.7",
|
|
37
37
|
"@babel/traverse": "7.29.7",
|
|
38
38
|
"@babel/types": "7.29.7",
|
|
39
|
-
"@cedarjs/api-server": "6.0.0-canary.
|
|
40
|
-
"@cedarjs/babel-config": "6.0.0-canary.
|
|
41
|
-
"@cedarjs/cli-helpers": "6.0.0-canary.
|
|
42
|
-
"@cedarjs/internal": "6.0.0-canary.
|
|
43
|
-
"@cedarjs/prerender": "6.0.0-canary.
|
|
44
|
-
"@cedarjs/project-config": "6.0.0-canary.
|
|
45
|
-
"@cedarjs/structure": "6.0.0-canary.
|
|
46
|
-
"@cedarjs/telemetry": "6.0.0-canary.
|
|
47
|
-
"@cedarjs/utils": "6.0.0-canary.
|
|
48
|
-
"@cedarjs/vite": "6.0.0-canary.
|
|
49
|
-
"@cedarjs/web-server": "6.0.0-canary.
|
|
39
|
+
"@cedarjs/api-server": "6.0.0-canary.2923",
|
|
40
|
+
"@cedarjs/babel-config": "6.0.0-canary.2923",
|
|
41
|
+
"@cedarjs/cli-helpers": "6.0.0-canary.2923",
|
|
42
|
+
"@cedarjs/internal": "6.0.0-canary.2923",
|
|
43
|
+
"@cedarjs/prerender": "6.0.0-canary.2923",
|
|
44
|
+
"@cedarjs/project-config": "6.0.0-canary.2923",
|
|
45
|
+
"@cedarjs/structure": "6.0.0-canary.2923",
|
|
46
|
+
"@cedarjs/telemetry": "6.0.0-canary.2923",
|
|
47
|
+
"@cedarjs/utils": "6.0.0-canary.2923",
|
|
48
|
+
"@cedarjs/vite": "6.0.0-canary.2923",
|
|
49
|
+
"@cedarjs/web-server": "6.0.0-canary.2923",
|
|
50
50
|
"@listr2/prompt-adapter-enquirer": "4.3.0",
|
|
51
51
|
"@opentelemetry/api": "1.9.1",
|
|
52
52
|
"@opentelemetry/core": "1.30.1",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"prettier": "3.8.4",
|
|
83
83
|
"prisma": "7.8.0",
|
|
84
84
|
"prompts": "2.4.2",
|
|
85
|
-
"semver": "7.
|
|
85
|
+
"semver": "7.8.5",
|
|
86
86
|
"smol-toml": "1.6.1",
|
|
87
87
|
"srvx": "0.11.16",
|
|
88
88
|
"string-env-interpolation": "1.0.1",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"yargs": "17.7.3"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@cedarjs/framework-tools": "6.0.0-canary.
|
|
97
|
+
"@cedarjs/framework-tools": "6.0.0-canary.2923",
|
|
98
98
|
"@prisma/dmmf": "7.8.0",
|
|
99
99
|
"@types/archiver": "^7.0.0",
|
|
100
100
|
"memfs": "4.68.1",
|