@cedarjs/cli 7.0.1-next.333 → 7.0.1-next.467
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/deploy/baremetal/baremetalHandler.js +363 -72
- package/dist/commands/deploy/baremetal/monitors.js +66 -0
- package/dist/commands/deploy/baremetal.js +6 -0
- package/dist/commands/destroy/scaffold/scaffoldHandler.js +63 -25
- package/dist/commands/dev/devHandler.js +13 -1
- package/dist/commands/generate/dbAuth/templates/login.webAuthn.tsx.template +20 -12
- package/dist/commands/generate/helpers.js +26 -0
- package/dist/commands/generate/ogImage/templates/ogImage.og.tsx.template +1 -1
- package/dist/commands/generate/package/templates/tsconfig.json.template +1 -0
- package/dist/commands/generate/realtime/templates/liveQueries/blank/blank.service.ts.template +12 -4
- package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.service.ts.template +2 -2
- package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.ts.template +3 -3
- package/dist/commands/generate/scaffold/scaffold.js +1 -1
- package/dist/commands/generate/scaffold/scaffoldHandler.js +83 -44
- package/dist/commands/generate/scaffold/templates/assets/scaffold.tailwind.css.template +12 -7
- package/dist/commands/generate/scaffold/templates/components/EditNameCell.tsx.template +7 -7
- package/dist/commands/generate/scaffold/templates/components/NameForm.tsx.template +7 -6
- package/dist/commands/generate/scaffold/templates/layouts/NamesScaffoldLayout.tsx.template +18 -0
- package/dist/commands/generate/scaffold/templates/lib/formatters.tsx.template +3 -3
- package/dist/commands/generate/script/templates/tsconfig.json.template +1 -0
- package/dist/commands/generate/sdl/sdlHandler.js +8 -3
- package/dist/commands/generate/service/serviceHandler.js +32 -11
- package/dist/commands/generate/service/templates/service.ts.template +21 -8
- package/dist/commands/generate/service/templates/test.ts.template +7 -5
- package/dist/commands/jobsHandler.js +5 -3
- package/dist/commands/setup/cache/templates/memcached.ts.template +14 -17
- package/dist/commands/setup/cache/templates/redis.ts.template +12 -20
- package/dist/commands/setup/deploy/helpers/index.js +3 -1
- package/dist/commands/setup/deploy/providers/baremetal.js +7 -0
- package/dist/commands/setup/deploy/providers/baremetalHandler.js +128 -20
- package/dist/commands/setup/deploy/providers/netlifyHandler.js +25 -3
- package/dist/commands/setup/deploy/templates/baremetal.js +119 -9
- package/dist/commands/setup/deploy/templates/netlifyUD.js +3 -0
- package/dist/commands/setup/jobs/jobsHandler.js +2 -0
- package/dist/commands/setup/monitoring/sentry/sentryHandler.js +5 -2
- package/dist/commands/setup/monitoring/sentry/templates/sentryApi.ts.template +1 -4
- package/dist/commands/setup/monitoring/sentry/templates/sentryWeb.ts.template +7 -4
- package/dist/commands/setup/realtime/templates/defer/fastAndSlowFields/fastAndSlowFields.ts.template +4 -1
- package/dist/commands/setup/realtime/templates/liveQueries/auctions/auctions.ts.template +12 -4
- package/dist/commands/setup/realtime/templates/subscriptions/newMessage/newMessage.ts.template +1 -1
- package/dist/commands/setup/tenancy/signupCodemod.js +2 -1
- package/dist/commands/setup/tenancy/templates/InvitePage.tsx.template +10 -1
- package/dist/commands/setup/tenancy/templates/organizations.scenarios.ts.template +0 -2
- package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +0 -8
- package/dist/commands/setup/tenancy/templates/organizations.ts.template +1 -1
- package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +1 -1
- package/dist/commands/setup/tenancy/tenancyHandler.js +1 -1
- package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +61 -81
- package/dist/commands/setup/uploads/gitignore.js +17 -0
- package/dist/commands/setup/uploads/templates/uploads.ts.template +14 -5
- package/dist/commands/setup/uploads/uploadsHandler.js +22 -1
- package/dist/lib/index.js +10 -6
- package/package.json +24 -24
- package/dist/commands/setup/ui/templates/postcss.config.cjs.template +0 -9
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
1
2
|
import { Listr } from "listr2";
|
|
2
|
-
import pascalcase from "pascalcase";
|
|
3
3
|
import { colors as c } from "@cedarjs/cli-helpers/colors";
|
|
4
4
|
import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
|
|
5
|
-
import { pluralize } from "@cedarjs/utils/cedarPluralize";
|
|
6
5
|
import {
|
|
7
6
|
deleteFilesTask,
|
|
8
7
|
getPaths,
|
|
@@ -13,6 +12,7 @@ import {
|
|
|
13
12
|
import { verifyModelName } from "../../../lib/schemaHelpers.js";
|
|
14
13
|
import {
|
|
15
14
|
files,
|
|
15
|
+
getScaffoldLayoutNames,
|
|
16
16
|
routes as scaffoldRoutes,
|
|
17
17
|
splitPathAndModel
|
|
18
18
|
} from "../../generate/scaffold/scaffoldHandler.js";
|
|
@@ -23,45 +23,83 @@ const removeRoutesWithSet = async ({
|
|
|
23
23
|
}) => {
|
|
24
24
|
const routes = await scaffoldRoutes({ model, path, nestScaffoldByModel });
|
|
25
25
|
const routeNames = routes.map(extractRouteName);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const { importName } = getScaffoldLayoutNames(model, path);
|
|
27
|
+
return removeRoutesFromRouterTask(routeNames, importName);
|
|
28
|
+
};
|
|
29
|
+
const removeNamedImport = (code, source, name) => {
|
|
30
|
+
const program = parse(code, {
|
|
31
|
+
sourceType: "module",
|
|
32
|
+
plugins: ["jsx", "typescript"]
|
|
33
|
+
}).program;
|
|
34
|
+
for (const statement of program.body) {
|
|
35
|
+
if (statement.type !== "ImportDeclaration" || statement.source.value !== source) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const specifiers = statement.specifiers;
|
|
39
|
+
const index = specifiers.findIndex(
|
|
40
|
+
(specifier2) => specifier2.type === "ImportSpecifier" && specifier2.imported.type === "Identifier" && specifier2.imported.name === name && specifier2.local.name === name
|
|
41
|
+
);
|
|
42
|
+
if (index === -1) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const specifier = specifiers[index];
|
|
46
|
+
let start;
|
|
47
|
+
let end;
|
|
48
|
+
if (specifiers.length === 1) {
|
|
49
|
+
start = statement.start ?? 0;
|
|
50
|
+
end = statement.end ?? 0;
|
|
51
|
+
if (code[end] === "\n") {
|
|
52
|
+
end++;
|
|
53
|
+
}
|
|
54
|
+
} else if (index === specifiers.length - 1) {
|
|
55
|
+
start = specifiers[index - 1].end ?? 0;
|
|
56
|
+
end = specifier.end ?? 0;
|
|
57
|
+
} else {
|
|
58
|
+
start = specifier.start ?? 0;
|
|
59
|
+
end = specifiers[index + 1].start ?? 0;
|
|
60
|
+
}
|
|
61
|
+
return code.slice(0, start) + code.slice(end);
|
|
62
|
+
}
|
|
63
|
+
return code;
|
|
29
64
|
};
|
|
30
65
|
const removeSetImport = () => {
|
|
31
66
|
const routesPath = getPaths().web.routes;
|
|
32
67
|
const routesContent = readFile(routesPath).toString();
|
|
33
|
-
|
|
68
|
+
const unusedNames = ["Set", "PrivateSet"].filter(
|
|
69
|
+
(name) => !new RegExp(`<${name}\\b`).test(routesContent)
|
|
70
|
+
);
|
|
71
|
+
if (unusedNames.length === 0) {
|
|
34
72
|
return "Skipping removal of Set import in Routes.{jsx,tsx}";
|
|
35
73
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
74
|
+
let newRoutesContent = routesContent;
|
|
75
|
+
try {
|
|
76
|
+
for (const name of unusedNames) {
|
|
77
|
+
newRoutesContent = removeNamedImport(
|
|
78
|
+
newRoutesContent,
|
|
79
|
+
"@cedarjs/router",
|
|
80
|
+
name
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
return "Skipping removal of Set import, Routes.{jsx,tsx} couldn't be parsed";
|
|
85
|
+
}
|
|
86
|
+
if (newRoutesContent === routesContent) {
|
|
87
|
+
return "No unused Set or PrivateSet import found in Routes.{jsx,tsx}";
|
|
41
88
|
}
|
|
42
|
-
const [cedarRouterImport] = cedarRouterImportMatch;
|
|
43
|
-
const removedSetImport = cedarRouterImport.replace(/,*\s*Set,*/, "");
|
|
44
|
-
const newRoutesContent = routesContent.replace(
|
|
45
|
-
cedarRouterImport,
|
|
46
|
-
removedSetImport
|
|
47
|
-
);
|
|
48
89
|
writeFile(routesPath, newRoutesContent, { overwriteExisting: true });
|
|
49
|
-
return "Removed Set
|
|
90
|
+
return "Removed unused Set and PrivateSet imports in Routes.{jsx,tsx}";
|
|
50
91
|
};
|
|
51
92
|
const removeLayoutImport = ({
|
|
52
93
|
model: name,
|
|
53
94
|
path: scaffoldPath = ""
|
|
54
95
|
}) => {
|
|
55
|
-
const
|
|
56
|
-
const pascalScaffoldPath = scaffoldPath === "" ? scaffoldPath : scaffoldPath.split("/").map(pascalcase).join("/") + "/";
|
|
57
|
-
const layoutName = `${pluralPascalName}Layout`;
|
|
58
|
-
const importLayout = `import ${pluralPascalName}Layout from 'src/layouts/${pascalScaffoldPath}${layoutName}'`;
|
|
96
|
+
const { importName, importPath } = getScaffoldLayoutNames(name, scaffoldPath);
|
|
59
97
|
const routesPath = getPaths().web.routes;
|
|
60
98
|
const routesContent = readFile(routesPath).toString();
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
""
|
|
99
|
+
const importLayout = new RegExp(
|
|
100
|
+
`\\s*import ${importName} from (['"])${importPath}\\1;?`
|
|
64
101
|
);
|
|
102
|
+
const newRoutesContent = routesContent.replace(importLayout, "");
|
|
65
103
|
writeFile(routesPath, newRoutesContent, { overwriteExisting: true });
|
|
66
104
|
return "Removed layout import from Routes.{jsx,tsx}";
|
|
67
105
|
};
|
|
@@ -185,21 +185,33 @@ const handler = async ({
|
|
|
185
185
|
const rootPackageJson = JSON.parse(
|
|
186
186
|
fs.readFileSync(rootPackageJsonPath, "utf8")
|
|
187
187
|
);
|
|
188
|
+
const warnUdFallback = (reason) => {
|
|
189
|
+
console.warn(
|
|
190
|
+
c.warning(
|
|
191
|
+
`--ud has no effect: ${reason}. Falling back to classic dev-server mode.`
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
};
|
|
188
195
|
const buildUnifiedDevCommand = () => {
|
|
189
196
|
if (!ud) {
|
|
190
197
|
return null;
|
|
191
198
|
}
|
|
192
199
|
if (streamingSsrEnabled) {
|
|
200
|
+
warnUdFallback("streaming SSR uses its own dev server");
|
|
193
201
|
return null;
|
|
194
202
|
}
|
|
195
203
|
if (!workspace.includes("api") || !workspace.includes("web")) {
|
|
204
|
+
warnUdFallback("the unified dev server needs both the api and web sides");
|
|
196
205
|
return null;
|
|
197
206
|
}
|
|
198
207
|
if (serverFile) {
|
|
208
|
+
warnUdFallback(
|
|
209
|
+
"the unified dev server does not support a custom server file"
|
|
210
|
+
);
|
|
199
211
|
return null;
|
|
200
212
|
}
|
|
201
213
|
if (!fs.existsSync(cedarPaths.api.src) || !fs.existsSync(cedarPaths.web.src)) {
|
|
202
|
-
|
|
214
|
+
warnUdFallback("api.src or web.src does not exist");
|
|
203
215
|
return null;
|
|
204
216
|
}
|
|
205
217
|
return [
|
|
@@ -17,7 +17,7 @@ import { useAuth } from 'src/auth'
|
|
|
17
17
|
const WELCOME_MESSAGE = 'Welcome back!'
|
|
18
18
|
const REDIRECT = routes.home()
|
|
19
19
|
|
|
20
|
-
const LoginPage = ({ type }) => {
|
|
20
|
+
const LoginPage = ({ type }: { type?: string }) => {
|
|
21
21
|
const {
|
|
22
22
|
isAuthenticated,
|
|
23
23
|
client: webAuthn,
|
|
@@ -27,12 +27,12 @@ const LoginPage = ({ type }) => {
|
|
|
27
27
|
} = useAuth()
|
|
28
28
|
const [shouldShowWebAuthn, setShouldShowWebAuthn] = useState(false)
|
|
29
29
|
const [showWebAuthn, setShowWebAuthn] = useState(
|
|
30
|
-
webAuthn
|
|
30
|
+
!!webAuthn?.isEnabled() && type !== 'password'
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
// should redirect right after login or wait to show the webAuthn prompts?
|
|
34
34
|
useEffect(() => {
|
|
35
|
-
if (isAuthenticated && (!shouldShowWebAuthn || webAuthn
|
|
35
|
+
if (isAuthenticated && (!shouldShowWebAuthn || webAuthn?.isEnabled())) {
|
|
36
36
|
navigate(REDIRECT)
|
|
37
37
|
}
|
|
38
38
|
}, [isAuthenticated, shouldShowWebAuthn])
|
|
@@ -45,13 +45,13 @@ const LoginPage = ({ type }) => {
|
|
|
45
45
|
}, [loading, isAuthenticated])
|
|
46
46
|
|
|
47
47
|
// focus on the ${usernameLowerCase} field as soon as the page loads
|
|
48
|
-
const ${usernameCamelCase}Ref = useRef()
|
|
48
|
+
const ${usernameCamelCase}Ref = useRef<HTMLInputElement>(null)
|
|
49
49
|
useEffect(() => {
|
|
50
|
-
${usernameCamelCase}Ref.current
|
|
50
|
+
${usernameCamelCase}Ref.current?.focus()
|
|
51
51
|
}, [])
|
|
52
52
|
|
|
53
|
-
const onSubmit = async (data) => {
|
|
54
|
-
const webAuthnSupported = await webAuthn
|
|
53
|
+
const onSubmit = async (data: Record<string, string>) => {
|
|
54
|
+
const webAuthnSupported = !!(await webAuthn?.isSupported())
|
|
55
55
|
|
|
56
56
|
if (webAuthnSupported) {
|
|
57
57
|
setShouldShowWebAuthn(true)
|
|
@@ -75,30 +75,38 @@ const LoginPage = ({ type }) => {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const onAuthenticate = async () => {
|
|
78
|
+
if (!webAuthn) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
78
82
|
try {
|
|
79
83
|
await webAuthn.authenticate()
|
|
80
84
|
await reauthenticate()
|
|
81
85
|
toast.success(WELCOME_MESSAGE)
|
|
82
86
|
navigate(REDIRECT)
|
|
83
87
|
} catch (e) {
|
|
84
|
-
if (e.name === 'WebAuthnDeviceNotFoundError') {
|
|
88
|
+
if (e instanceof Error && e.name === 'WebAuthnDeviceNotFoundError') {
|
|
85
89
|
toast.error(
|
|
86
90
|
'Device not found, log in with ${usernameTitleCase}/${passwordTitleCase} to continue'
|
|
87
91
|
)
|
|
88
92
|
setShowWebAuthn(false)
|
|
89
93
|
} else {
|
|
90
|
-
toast.error(e.message)
|
|
94
|
+
toast.error(e instanceof Error ? e.message : String(e))
|
|
91
95
|
}
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
const onRegister = async () => {
|
|
100
|
+
if (!webAuthn) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
96
104
|
try {
|
|
97
105
|
await webAuthn.register()
|
|
98
106
|
toast.success(WELCOME_MESSAGE)
|
|
99
107
|
navigate(REDIRECT)
|
|
100
108
|
} catch (e) {
|
|
101
|
-
toast.error(e.message)
|
|
109
|
+
toast.error(e instanceof Error ? e.message : String(e))
|
|
102
110
|
}
|
|
103
111
|
}
|
|
104
112
|
|
|
@@ -200,7 +208,7 @@ const LoginPage = ({ type }) => {
|
|
|
200
208
|
|
|
201
209
|
const formToRender = () => {
|
|
202
210
|
if (showWebAuthn) {
|
|
203
|
-
if (webAuthn
|
|
211
|
+
if (webAuthn?.isEnabled()) {
|
|
204
212
|
return <AuthWebAuthnPrompt />
|
|
205
213
|
} else {
|
|
206
214
|
return <RegisterWebAuthnPrompt />
|
|
@@ -212,7 +220,7 @@ const LoginPage = ({ type }) => {
|
|
|
212
220
|
|
|
213
221
|
const linkToRender = () => {
|
|
214
222
|
if (showWebAuthn) {
|
|
215
|
-
if (webAuthn
|
|
223
|
+
if (webAuthn?.isEnabled()) {
|
|
216
224
|
return (
|
|
217
225
|
<div className="rw-login-link">
|
|
218
226
|
<span>or login with </span>{' '}
|
|
@@ -37,11 +37,33 @@ const redactedModelFields = (fieldNames) => {
|
|
|
37
37
|
}
|
|
38
38
|
return sensitive;
|
|
39
39
|
};
|
|
40
|
+
const DEFAULT_IGNORE_FIELDS_FOR_INPUT = ["createdAt", "updatedAt"];
|
|
41
|
+
const requiredFieldsInUpdateInput = (model) => {
|
|
42
|
+
return inputFields(model).filter((field) => {
|
|
43
|
+
return field.isRequired && !field.isList && !field.isId;
|
|
44
|
+
}).map((field) => field.name);
|
|
45
|
+
};
|
|
46
|
+
const nullableJsonFieldsInInput = (model) => {
|
|
47
|
+
return inputFields(model).filter((field) => {
|
|
48
|
+
return field.type === "Json" && !field.isRequired && !field.isList;
|
|
49
|
+
}).map((field) => field.name);
|
|
50
|
+
};
|
|
51
|
+
const inputFields = (model) => {
|
|
52
|
+
const redactedFields = redactedModelFields(
|
|
53
|
+
model.fields.map((field) => field.name)
|
|
54
|
+
);
|
|
55
|
+
return model.fields.filter((field) => {
|
|
56
|
+
return field.kind !== "object" && !DEFAULT_IGNORE_FIELDS_FOR_INPUT.includes(field.name) && !redactedFields.includes(field.name);
|
|
57
|
+
});
|
|
58
|
+
};
|
|
40
59
|
const relationsForModel = (model) => {
|
|
41
60
|
return model?.fields.filter((f) => f.relationName).map((field) => {
|
|
42
61
|
return field.name;
|
|
43
62
|
});
|
|
44
63
|
};
|
|
64
|
+
const nullableRelationsForModel = (model) => {
|
|
65
|
+
return model.fields.filter((f) => f.kind === "object" && !f.isList && !f.isRequired).map((f) => f.name);
|
|
66
|
+
};
|
|
45
67
|
const intForeignKeysForModel = (model) => {
|
|
46
68
|
return model.fields.filter((f) => f.name.match(/Id$/) && f.type === "Int").map((f) => f.name);
|
|
47
69
|
};
|
|
@@ -74,14 +96,18 @@ const mapPrismaScalarToPagePropTsType = (scalarType) => {
|
|
|
74
96
|
return prismaScalarToTsType[scalarType] || "unknown";
|
|
75
97
|
};
|
|
76
98
|
export {
|
|
99
|
+
DEFAULT_IGNORE_FIELDS_FOR_INPUT,
|
|
77
100
|
SENSITIVE_FIELDS,
|
|
78
101
|
forcePluralizeWord,
|
|
79
102
|
intForeignKeysForModel,
|
|
80
103
|
mapPrismaScalarToPagePropTsType,
|
|
81
104
|
mapRouteParamTypeToTsType,
|
|
105
|
+
nullableJsonFieldsInInput,
|
|
106
|
+
nullableRelationsForModel,
|
|
82
107
|
pathName,
|
|
83
108
|
redactedModelFields,
|
|
84
109
|
relationsForModel,
|
|
85
110
|
removeGeneratorName,
|
|
111
|
+
requiredFieldsInUpdateInput,
|
|
86
112
|
validateName
|
|
87
113
|
};
|
package/dist/commands/generate/realtime/templates/liveQueries/blank/blank.service.ts.template
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
// api/src/services/${name}s/${name}s.ts
|
|
2
|
+
import type { ${typeName}ItemInput } from 'types/graphql'
|
|
3
|
+
|
|
2
4
|
import type { LiveQueryStorageMechanism } from '@cedarjs/realtime'
|
|
3
5
|
|
|
4
6
|
import { logger } from 'src/lib/logger'
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
interface ${typeName}Data {
|
|
9
|
+
id: string
|
|
10
|
+
title: string
|
|
11
|
+
items: { amount: number }[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ${collectionName}: ${typeName}Data[] = [
|
|
7
15
|
{ id: '1', title: 'First ${modelName}', items: [{ amount: 11 }] },
|
|
8
16
|
{ id: '2', title: '2nd ${modelName}', items: [{ amount: 22 }] },
|
|
9
17
|
{ id: '3', title: 'Third ${modelName}', items: [{ amount: 33 }] },
|
|
@@ -35,14 +43,14 @@ const ${collectionName} = [
|
|
|
35
43
|
* }
|
|
36
44
|
* }
|
|
37
45
|
*/
|
|
38
|
-
export const ${liveQueryName} = async ({ id }) => {
|
|
46
|
+
export const ${liveQueryName} = async ({ id }: { id: string }) => {
|
|
39
47
|
const found${modelName} = ${collectionName}.find((a) => a.id === id)
|
|
40
48
|
logger.debug({ id, ${name}: found${modelName} }, `${name} details`)
|
|
41
49
|
return found${modelName}
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
export const create${typeName}Item = async (
|
|
45
|
-
{ input },
|
|
53
|
+
{ input }: { input: ${typeName}ItemInput },
|
|
46
54
|
{ context }: { context: { liveQueryStore: LiveQueryStorageMechanism } }
|
|
47
55
|
) => {
|
|
48
56
|
const { ${camelName}Id, amount } = input
|
|
@@ -63,7 +71,7 @@ export const create${typeName}Item = async (
|
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
export const ${modelName} = {
|
|
66
|
-
largest${typeName}Item: (obj, { root }) => {
|
|
74
|
+
largest${typeName}Item: (obj: unknown, { root }: { root: ${typeName}Data }) => {
|
|
67
75
|
const [largest] = root.items.sort((a, b) => b.amount - a.amount)
|
|
68
76
|
|
|
69
77
|
logger.debug({ obj, root }, 'largest ${typeName}Item')
|
package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.service.ts.template
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ${subscriptionInputType} } from 'types/graphql'
|
|
2
2
|
|
|
3
3
|
import { logger } from 'src/lib/logger'
|
|
4
|
-
import type { Publish${typeName}ChannelType } from 'src/subscriptions/${name}/${name}.
|
|
4
|
+
import type { Publish${typeName}ChannelType } from 'src/subscriptions/${name}/${name}.js'
|
|
5
5
|
|
|
6
|
-
export const ${subscriptionQueryName} = ({ id }) => [id]
|
|
6
|
+
export const ${subscriptionQueryName} = ({ id }: { id: string }) => [id]
|
|
7
7
|
|
|
8
8
|
export const ${subscriptionServiceResolver} = async (
|
|
9
9
|
{ input }: { input: ${subscriptionInputType} },
|
|
@@ -39,15 +39,15 @@ export type Publish${typeName}ChannelType = PubSub<Publish${typeName}Channel>
|
|
|
39
39
|
const ${subscriptionName}Subscription = {
|
|
40
40
|
${subscriptionName}: {
|
|
41
41
|
subscribe: (
|
|
42
|
-
_,
|
|
43
|
-
{ id },
|
|
42
|
+
_: unknown,
|
|
43
|
+
{ id }: { id: string },
|
|
44
44
|
{ pubSub }: { pubSub: Publish${typeName}ChannelType }
|
|
45
45
|
) => {
|
|
46
46
|
logger.debug({ id }, '${name} subscription')
|
|
47
47
|
|
|
48
48
|
return pubSub.subscribe('${typeName}', id)
|
|
49
49
|
},
|
|
50
|
-
resolve: (payload) => {
|
|
50
|
+
resolve: (payload: Publish${typeName}Channel['${typeName}'][1]) => {
|
|
51
51
|
logger.debug({ payload }, '${name} subscription resolve')
|
|
52
52
|
|
|
53
53
|
return payload
|
|
@@ -13,7 +13,7 @@ const builder = (yargs) => {
|
|
|
13
13
|
description: "Generate test files",
|
|
14
14
|
type: "boolean"
|
|
15
15
|
}).option("tailwind", {
|
|
16
|
-
description: "Generate TailwindCSS version of scaffold.css (automatically set to `true` if
|
|
16
|
+
description: "Generate TailwindCSS version of scaffold.css (automatically set to `true` if the web side depends on `tailwindcss`)",
|
|
17
17
|
type: "boolean"
|
|
18
18
|
}).option("rollback", {
|
|
19
19
|
description: "Revert all generator actions if an error occurs",
|
|
@@ -86,6 +86,18 @@ const getImportComponentNames = (name, scaffoldPath, nestScaffoldByModel = true)
|
|
|
86
86
|
importLayoutNames: `src/layouts/ScaffoldLayout`
|
|
87
87
|
};
|
|
88
88
|
};
|
|
89
|
+
const getScaffoldLayoutNames = (name, scaffoldPath = "") => {
|
|
90
|
+
const pluralPascalName = pascalcase(pluralize(name));
|
|
91
|
+
const directoryName = `${pluralPascalName}ScaffoldLayout`;
|
|
92
|
+
const pathParts = scaffoldPath === "" ? [] : scaffoldPath.split("/").map(pascalcase);
|
|
93
|
+
return {
|
|
94
|
+
directoryName,
|
|
95
|
+
pathParts,
|
|
96
|
+
// The name to import the layout as in Routes.{jsx,tsx}
|
|
97
|
+
importName: pascalcase(scaffoldPath) + directoryName,
|
|
98
|
+
importPath: ["src/layouts", ...pathParts, directoryName].join("/")
|
|
99
|
+
};
|
|
100
|
+
};
|
|
89
101
|
const getTemplateStrings = (name, scaffoldPath, nestScaffoldByModel = true) => {
|
|
90
102
|
const nameVars = nameVariants(name);
|
|
91
103
|
const camelScaffoldPath = camelcase(pascalcase(scaffoldPath));
|
|
@@ -97,11 +109,21 @@ const getTemplateStrings = (name, scaffoldPath, nestScaffoldByModel = true) => {
|
|
|
97
109
|
...getImportComponentNames(name, scaffoldPath, nestScaffoldByModel)
|
|
98
110
|
};
|
|
99
111
|
};
|
|
112
|
+
const webDependsOnTailwindCSS = () => {
|
|
113
|
+
const webPackageJsonPath = path.join(getPaths().web.base, "package.json");
|
|
114
|
+
if (!fs.existsSync(webPackageJsonPath)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const { dependencies, devDependencies } = JSON.parse(
|
|
118
|
+
fs.readFileSync(webPackageJsonPath, "utf-8")
|
|
119
|
+
);
|
|
120
|
+
return Boolean(
|
|
121
|
+
dependencies?.["tailwindcss"] || devDependencies?.["tailwindcss"]
|
|
122
|
+
);
|
|
123
|
+
};
|
|
100
124
|
const shouldUseTailwindCSS = (flag) => {
|
|
101
125
|
if (flag === void 0) {
|
|
102
|
-
return
|
|
103
|
-
path.join(getPaths().web.config, "tailwind.config.cjs")
|
|
104
|
-
);
|
|
126
|
+
return webDependsOnTailwindCSS();
|
|
105
127
|
} else {
|
|
106
128
|
return flag;
|
|
107
129
|
}
|
|
@@ -150,7 +172,13 @@ const files = async ({
|
|
|
150
172
|
}),
|
|
151
173
|
...await assetFiles(name, tailwind),
|
|
152
174
|
...await formatters(name, typescript),
|
|
153
|
-
...await layoutFiles(
|
|
175
|
+
...await layoutFiles(
|
|
176
|
+
name,
|
|
177
|
+
scaffoldPath,
|
|
178
|
+
force,
|
|
179
|
+
typescript,
|
|
180
|
+
templateStrings
|
|
181
|
+
),
|
|
154
182
|
...await pageFiles(
|
|
155
183
|
name,
|
|
156
184
|
pascalScaffoldPath,
|
|
@@ -348,41 +376,57 @@ const modelRelatedVariables = (model) => {
|
|
|
348
376
|
parsedDateTime: isDateTimeParsed()
|
|
349
377
|
};
|
|
350
378
|
};
|
|
351
|
-
const layoutFiles = async (name, force, generateTypescript, templateStrings) => {
|
|
379
|
+
const layoutFiles = async (name, scaffoldPath, force, generateTypescript, templateStrings) => {
|
|
352
380
|
const fileList = {};
|
|
353
|
-
const
|
|
381
|
+
const extension = generateTypescript ? ".tsx" : ".jsx";
|
|
382
|
+
const renderLayout = async (templateName, outputPath) => {
|
|
383
|
+
const template = await generateTemplate(
|
|
384
|
+
customOrDefaultTemplatePath({
|
|
385
|
+
side: "web",
|
|
386
|
+
generator: "scaffold",
|
|
387
|
+
templatePath: path.join("layouts", templateName)
|
|
388
|
+
}),
|
|
389
|
+
{
|
|
390
|
+
name,
|
|
391
|
+
pascalScaffoldPath: "",
|
|
392
|
+
...templateStrings
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
|
|
396
|
+
};
|
|
397
|
+
const sharedLayoutDir = path.join(getPaths().web.layouts, "ScaffoldLayout");
|
|
398
|
+
const sharedLayoutTemplates = fs.readdirSync(
|
|
354
399
|
customOrDefaultTemplatePath({
|
|
355
400
|
side: "web",
|
|
356
401
|
generator: "scaffold",
|
|
357
402
|
templatePath: "layouts"
|
|
358
403
|
})
|
|
359
|
-
);
|
|
360
|
-
for (const
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (!fs.existsSync(outputPath) || force) {
|
|
371
|
-
const template = await generateTemplate(
|
|
372
|
-
customOrDefaultTemplatePath({
|
|
373
|
-
side: "web",
|
|
374
|
-
generator: "scaffold",
|
|
375
|
-
templatePath: path.join("layouts", layout)
|
|
376
|
-
}),
|
|
377
|
-
{
|
|
378
|
-
name,
|
|
379
|
-
pascalScaffoldPath: "",
|
|
380
|
-
...templateStrings
|
|
381
|
-
}
|
|
382
|
-
);
|
|
383
|
-
fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
|
|
404
|
+
).filter((template) => template !== "NamesScaffoldLayout.tsx.template");
|
|
405
|
+
for (const template of sharedLayoutTemplates) {
|
|
406
|
+
const isComponent = template.endsWith(".tsx.template");
|
|
407
|
+
const outputName = isComponent ? template.replace(/\.tsx\.template$/, extension) : template.replace(/\.template$/, "");
|
|
408
|
+
const exists = isComponent ? [".tsx", ".jsx", ".ts", ".js"].some(
|
|
409
|
+
(ext) => fs.existsSync(
|
|
410
|
+
path.join(sharedLayoutDir, outputName.replace(extension, ext))
|
|
411
|
+
)
|
|
412
|
+
) : fs.existsSync(path.join(sharedLayoutDir, outputName));
|
|
413
|
+
if (!exists || force) {
|
|
414
|
+
await renderLayout(template, path.join(sharedLayoutDir, outputName));
|
|
384
415
|
}
|
|
385
416
|
}
|
|
417
|
+
const { directoryName, pathParts } = getScaffoldLayoutNames(
|
|
418
|
+
name,
|
|
419
|
+
scaffoldPath
|
|
420
|
+
);
|
|
421
|
+
await renderLayout(
|
|
422
|
+
"NamesScaffoldLayout.tsx.template",
|
|
423
|
+
path.join(
|
|
424
|
+
getPaths().web.layouts,
|
|
425
|
+
...pathParts,
|
|
426
|
+
directoryName,
|
|
427
|
+
directoryName + extension
|
|
428
|
+
)
|
|
429
|
+
);
|
|
386
430
|
return fileList;
|
|
387
431
|
};
|
|
388
432
|
const pageFiles = async (name, pascalScaffoldPath = "", generateTypescript, nestScaffoldByModel = true, templateStrings) => {
|
|
@@ -424,7 +468,7 @@ const pageFiles = async (name, pascalScaffoldPath = "", generateTypescript, nest
|
|
|
424
468
|
...modelRelatedVariables(model)
|
|
425
469
|
}
|
|
426
470
|
);
|
|
427
|
-
fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
|
|
471
|
+
fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
|
|
428
472
|
}
|
|
429
473
|
return fileList;
|
|
430
474
|
};
|
|
@@ -472,7 +516,7 @@ const componentFiles = async (name, pascalScaffoldPath = "", generateTypescript,
|
|
|
472
516
|
...modelRelatedVariables(model)
|
|
473
517
|
}
|
|
474
518
|
);
|
|
475
|
-
fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
|
|
519
|
+
fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
|
|
476
520
|
}
|
|
477
521
|
return fileList;
|
|
478
522
|
};
|
|
@@ -503,8 +547,9 @@ const routes = async ({
|
|
|
503
547
|
`<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}" page={${pageRoot}${nameVars.pluralPascalName}Page} name="${templateNames.pluralRouteName}" />`
|
|
504
548
|
];
|
|
505
549
|
};
|
|
506
|
-
const addLayoutImport = () => {
|
|
507
|
-
const
|
|
550
|
+
const addLayoutImport = (model, scaffoldPath) => {
|
|
551
|
+
const { importName, importPath } = getScaffoldLayoutNames(model, scaffoldPath);
|
|
552
|
+
const importLayout = `import ${importName} from '${importPath}'`;
|
|
508
553
|
const routesPath = getPaths().web.routes;
|
|
509
554
|
const routesContent = readFile(routesPath).toString();
|
|
510
555
|
if (!routesContent.match(importLayout)) {
|
|
@@ -618,17 +663,10 @@ const addSetImport = (task) => {
|
|
|
618
663
|
return `Added ${namesToImport.join(", ")} import to Routes.{jsx,tsx}`;
|
|
619
664
|
};
|
|
620
665
|
const addScaffoldSetToRouter = async (model, scaffoldPath) => {
|
|
621
|
-
const templateNames = getTemplateStrings(model, scaffoldPath);
|
|
622
|
-
const nameVars = nameVariants(model);
|
|
623
|
-
const title = nameVars.pluralPascalName;
|
|
624
|
-
const titleTo = templateNames.pluralRouteName;
|
|
625
|
-
const buttonLabel = `New ${nameVars.singularPascalName}`;
|
|
626
|
-
const buttonTo = templateNames.newRouteName;
|
|
627
666
|
const unauthenticatedRoute = getUnauthenticatedRedirectRoute();
|
|
628
667
|
return addRoutesToRouterTask(
|
|
629
668
|
await routes({ model, path: scaffoldPath }),
|
|
630
|
-
|
|
631
|
-
{ title, titleTo, buttonLabel, buttonTo },
|
|
669
|
+
getScaffoldLayoutNames(model, scaffoldPath).importName,
|
|
632
670
|
unauthenticatedRoute ? { unauthenticated: unauthenticatedRoute } : void 0
|
|
633
671
|
);
|
|
634
672
|
};
|
|
@@ -669,7 +707,7 @@ const tasks = ({
|
|
|
669
707
|
},
|
|
670
708
|
{
|
|
671
709
|
title: "Adding layout import...",
|
|
672
|
-
task: async () => addLayoutImport()
|
|
710
|
+
task: async () => addLayoutImport(model, path2)
|
|
673
711
|
},
|
|
674
712
|
{
|
|
675
713
|
title: "Adding set import...",
|
|
@@ -785,6 +823,7 @@ const splitPathAndModel = (pathSlashModel) => {
|
|
|
785
823
|
};
|
|
786
824
|
export {
|
|
787
825
|
files,
|
|
826
|
+
getScaffoldLayoutNames,
|
|
788
827
|
getUnauthenticatedRedirectRoute,
|
|
789
828
|
handler,
|
|
790
829
|
hasLoginRoute,
|