@cedarjs/cli 8.0.0-canary.3274 → 8.0.0-canary.3285
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 +12 -4
- package/dist/commands/generate/dbAuth/dbAuth.js +6 -2
- package/dist/commands/generate/dbAuth/dbAuthHandler.js +57 -14
- package/dist/commands/generate/dbAuth/templates/changePassword.tsx.template +131 -0
- package/dist/commands/generate/directive/directive.js +15 -2
- package/dist/commands/setup/uploads/serverFile.js +93 -27
- package/package.json +13 -13
|
@@ -14,6 +14,8 @@ const CONFIG_FILENAME = "deploy.toml";
|
|
|
14
14
|
const SYMLINK_FLAGS = "-nsf";
|
|
15
15
|
const CURRENT_RELEASE_SYMLINK_NAME = "current";
|
|
16
16
|
const LIFECYCLE_HOOKS = ["before", "after"];
|
|
17
|
+
const RELEASE_DIR_PATTERN = "^[0-9]{14}$";
|
|
18
|
+
const releaseDirRegExp = new RegExp(RELEASE_DIR_PATTERN);
|
|
17
19
|
const DEFAULT_SERVER_CONFIG = {
|
|
18
20
|
port: 22,
|
|
19
21
|
branch: "main",
|
|
@@ -141,8 +143,13 @@ const rollbackTasks = (count, ssh, serverConfig) => {
|
|
|
141
143
|
title: `Rolling back ${rollbackCount} release(s)...`,
|
|
142
144
|
task: async () => {
|
|
143
145
|
const currentLink = (await ssh.exec(serverConfig.path, "readlink", ["-f", "current"])).stdout.split("/").pop();
|
|
144
|
-
const dirs = (await ssh.exec(serverConfig.path, "ls", ["-t"])).stdout.split("\n").filter((
|
|
146
|
+
const dirs = (await ssh.exec(serverConfig.path, "ls", ["-t"])).stdout.split("\n").filter((dir) => releaseDirRegExp.test(dir));
|
|
145
147
|
const deployedIndex = dirs.indexOf(currentLink ?? "");
|
|
148
|
+
if (deployedIndex === -1) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Cannot rollback: \`current\` points to "${currentLink}", which is not a timestamp-named release directory`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
146
153
|
const rollbackIndex = deployedIndex + rollbackCount;
|
|
147
154
|
if (dirs[rollbackIndex]) {
|
|
148
155
|
console.info("Setting symlink");
|
|
@@ -153,7 +160,7 @@ const rollbackTasks = (count, ssh, serverConfig) => {
|
|
|
153
160
|
);
|
|
154
161
|
} else {
|
|
155
162
|
throw new Error(
|
|
156
|
-
`Cannot rollback ${rollbackCount} release(s): ${dirs.length -
|
|
163
|
+
`Cannot rollback ${rollbackCount} release(s): ${dirs.length - deployedIndex - 1} previous release(s) available`
|
|
157
164
|
);
|
|
158
165
|
}
|
|
159
166
|
}
|
|
@@ -425,10 +432,10 @@ const deployTasks = (yargs, ssh, serverConfig, serverLifecycle) => {
|
|
|
425
432
|
command: {
|
|
426
433
|
title: `Cleaning up old deploys...`,
|
|
427
434
|
task: async () => {
|
|
428
|
-
const fileStartIndex = serverConfig.keepReleases +
|
|
435
|
+
const fileStartIndex = serverConfig.keepReleases + 1;
|
|
429
436
|
await ssh.exec(
|
|
430
437
|
serverConfig.path,
|
|
431
|
-
`ls -t | tail -n +${fileStartIndex} | xargs rm -rf`
|
|
438
|
+
`ls -t | grep -E '${RELEASE_DIR_PATTERN}' | tail -n +${fileStartIndex} | xargs -r rm -rf`
|
|
432
439
|
);
|
|
433
440
|
}
|
|
434
441
|
}
|
|
@@ -587,6 +594,7 @@ const handler = async (yargs) => {
|
|
|
587
594
|
};
|
|
588
595
|
export {
|
|
589
596
|
DEFAULT_SERVER_CONFIG,
|
|
597
|
+
RELEASE_DIR_PATTERN,
|
|
590
598
|
commandWithLifecycleEvents,
|
|
591
599
|
commands,
|
|
592
600
|
deployTasks,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { terminalLink } from "termi-link";
|
|
2
2
|
import { getYargsDefaults, createHandler } from "../yargsCommandHelpers.js";
|
|
3
3
|
const command = "dbAuth";
|
|
4
|
-
const description = "Generate Login, Signup and
|
|
4
|
+
const description = "Generate Login, Signup, Forgot Password, Reset Password and Change Password pages for dbAuth";
|
|
5
5
|
const builder = (yargs) => {
|
|
6
|
-
yargs.option("skip-
|
|
6
|
+
yargs.option("skip-change", {
|
|
7
|
+
description: "Skip generating the Change Password page",
|
|
8
|
+
type: "boolean",
|
|
9
|
+
default: false
|
|
10
|
+
}).option("skip-forgot", {
|
|
7
11
|
description: "Skip generating the Forgot Password page",
|
|
8
12
|
type: "boolean",
|
|
9
13
|
default: false
|
|
@@ -18,12 +18,25 @@ import {
|
|
|
18
18
|
} from "../../../lib/index.js";
|
|
19
19
|
import { prepareForRollback } from "../../../lib/rollback.js";
|
|
20
20
|
import { templateForComponentFile } from "../yargsHandlerHelpers.js";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
function routesFor({
|
|
22
|
+
skipChange,
|
|
23
|
+
skipForgot,
|
|
24
|
+
skipLogin,
|
|
25
|
+
skipReset,
|
|
26
|
+
skipSignup
|
|
27
|
+
}) {
|
|
28
|
+
return [
|
|
29
|
+
!skipLogin && `<Route path="/login" page={LoginPage} name="login" />`,
|
|
30
|
+
!skipSignup && `<Route path="/signup" page={SignupPage} name="signup" />`,
|
|
31
|
+
!skipForgot && `<Route path="/forgot-password" page={ForgotPasswordPage} name="forgotPassword" />`,
|
|
32
|
+
!skipReset && `<Route path="/reset-password" page={ResetPasswordPage} name="resetPassword" />`,
|
|
33
|
+
!skipChange && `<Route path="/change-password" page={ChangePasswordPage} name="changePassword" />`
|
|
34
|
+
].filter((route) => typeof route === "string");
|
|
35
|
+
}
|
|
36
|
+
function pascalCase(str) {
|
|
37
|
+
const camelCased = camelCase(str);
|
|
38
|
+
return camelCased.charAt(0).toUpperCase() + camelCased.slice(1);
|
|
39
|
+
}
|
|
27
40
|
function getPostInstallMessage(isDbAuthSetup2) {
|
|
28
41
|
return [
|
|
29
42
|
` ${c.warning("Pages created! But you're not done yet:")}
|
|
@@ -35,9 +48,11 @@ function getPostInstallMessage(isDbAuthSetup2) {
|
|
|
35
48
|
" navigate(routes.home())",
|
|
36
49
|
" }\n",
|
|
37
50
|
" and change the route to where you want them to go if the user is already",
|
|
38
|
-
" logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage",
|
|
39
|
-
" and
|
|
40
|
-
" those forms.\n",
|
|
51
|
+
" logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage,",
|
|
52
|
+
" ResetPasswordPage and ChangePasswordPage to change where the user redirects",
|
|
53
|
+
" to after submitting those forms.\n",
|
|
54
|
+
" ChangePasswordPage is for users who are already logged in. Link to it from",
|
|
55
|
+
" wherever your app lets users manage their account.\n",
|
|
41
56
|
!isDbAuthSetup2 && ` Oh, and if you haven't already, add the necessary dbAuth functions and
|
|
42
57
|
app setup by running:
|
|
43
58
|
|
|
@@ -58,9 +73,11 @@ function getPostInstallWebauthnMessage(isDbAuthSetup2) {
|
|
|
58
73
|
" navigate(routes.home())",
|
|
59
74
|
" }\n",
|
|
60
75
|
" and change the route to where you want them to go if the user is already",
|
|
61
|
-
" logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage",
|
|
62
|
-
" and
|
|
63
|
-
" those forms.\n",
|
|
76
|
+
" logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage,",
|
|
77
|
+
" ResetPasswordPage and ChangePasswordPage to change where the user redirects",
|
|
78
|
+
" to after submitting those forms.\n",
|
|
79
|
+
" ChangePasswordPage is for users who are already logged in. Link to it from",
|
|
80
|
+
" wherever your app lets users manage their account.\n",
|
|
64
81
|
!isDbAuthSetup2 && ` Oh, and if you haven't already, add the necessary dbAuth functions and
|
|
65
82
|
app setup by running:
|
|
66
83
|
|
|
@@ -71,6 +88,7 @@ function getPostInstallWebauthnMessage(isDbAuthSetup2) {
|
|
|
71
88
|
}
|
|
72
89
|
const files = async ({
|
|
73
90
|
typescript,
|
|
91
|
+
skipChange,
|
|
74
92
|
skipForgot,
|
|
75
93
|
skipLogin,
|
|
76
94
|
skipReset,
|
|
@@ -90,7 +108,8 @@ const files = async ({
|
|
|
90
108
|
usernameTitleCase: titleCase(usernameLabel),
|
|
91
109
|
passwordLowerCase: passwordLabel.toLowerCase(),
|
|
92
110
|
passwordCamelCase: camelCase(passwordLabel),
|
|
93
|
-
passwordTitleCase: titleCase(passwordLabel)
|
|
111
|
+
passwordTitleCase: titleCase(passwordLabel),
|
|
112
|
+
passwordPascalCase: pascalCase(passwordLabel)
|
|
94
113
|
};
|
|
95
114
|
if (!skipForgot) {
|
|
96
115
|
filesList.push(
|
|
@@ -131,6 +150,19 @@ const files = async ({
|
|
|
131
150
|
})
|
|
132
151
|
);
|
|
133
152
|
}
|
|
153
|
+
if (!skipChange) {
|
|
154
|
+
filesList.push(
|
|
155
|
+
await templateForComponentFile({
|
|
156
|
+
name: "ChangePassword",
|
|
157
|
+
suffix: "Page",
|
|
158
|
+
extension: typescript ? ".tsx" : ".jsx",
|
|
159
|
+
webPathSection: "pages",
|
|
160
|
+
generator: "dbAuth",
|
|
161
|
+
templatePath: "changePassword.tsx.template",
|
|
162
|
+
templateVars
|
|
163
|
+
})
|
|
164
|
+
);
|
|
165
|
+
}
|
|
134
166
|
if (!skipSignup) {
|
|
135
167
|
filesList.push(
|
|
136
168
|
await templateForComponentFile({
|
|
@@ -179,6 +211,7 @@ const tasks = ({
|
|
|
179
211
|
listr2,
|
|
180
212
|
force,
|
|
181
213
|
typescript,
|
|
214
|
+
skipChange,
|
|
182
215
|
skipForgot,
|
|
183
216
|
skipLogin,
|
|
184
217
|
skipReset,
|
|
@@ -289,6 +322,7 @@ const tasks = ({
|
|
|
289
322
|
task: async () => {
|
|
290
323
|
const filesObj = await files({
|
|
291
324
|
typescript,
|
|
325
|
+
skipChange,
|
|
292
326
|
skipForgot,
|
|
293
327
|
skipLogin,
|
|
294
328
|
skipReset,
|
|
@@ -305,7 +339,15 @@ const tasks = ({
|
|
|
305
339
|
{
|
|
306
340
|
title: "Adding routes...",
|
|
307
341
|
task: async () => {
|
|
308
|
-
addRoutesToRouterTask(
|
|
342
|
+
addRoutesToRouterTask(
|
|
343
|
+
routesFor({
|
|
344
|
+
skipChange,
|
|
345
|
+
skipForgot,
|
|
346
|
+
skipLogin,
|
|
347
|
+
skipReset,
|
|
348
|
+
skipSignup
|
|
349
|
+
})
|
|
350
|
+
);
|
|
309
351
|
}
|
|
310
352
|
},
|
|
311
353
|
{
|
|
@@ -335,6 +377,7 @@ const tasks = ({
|
|
|
335
377
|
const handler = async (yargs) => {
|
|
336
378
|
recordTelemetryAttributes({
|
|
337
379
|
command: "generate dbAuth",
|
|
380
|
+
skipChange: yargs.skipChange,
|
|
338
381
|
skipForgot: yargs.skipForgot,
|
|
339
382
|
skipLogin: yargs.skipLogin,
|
|
340
383
|
skipReset: yargs.skipReset,
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Form,
|
|
5
|
+
Label,
|
|
6
|
+
PasswordField,
|
|
7
|
+
Submit,
|
|
8
|
+
FieldError,
|
|
9
|
+
} from '@cedarjs/forms'
|
|
10
|
+
import { navigate, routes } from '@cedarjs/router'
|
|
11
|
+
import { Metadata } from '@cedarjs/web'
|
|
12
|
+
import { toast, Toaster } from '@cedarjs/web/toast'
|
|
13
|
+
|
|
14
|
+
import { useAuth } from 'src/auth'
|
|
15
|
+
|
|
16
|
+
const ChangePasswordPage = () => {
|
|
17
|
+
const { loading, isAuthenticated, reauthenticate, changePassword } =
|
|
18
|
+
useAuth()
|
|
19
|
+
|
|
20
|
+
// Only a logged in user can change their password
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!loading && !isAuthenticated) {
|
|
23
|
+
navigate(routes.login())
|
|
24
|
+
}
|
|
25
|
+
}, [loading, isAuthenticated])
|
|
26
|
+
|
|
27
|
+
const current${passwordPascalCase}Ref = useRef<HTMLInputElement>(null)
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
current${passwordPascalCase}Ref.current?.focus()
|
|
30
|
+
}, [])
|
|
31
|
+
|
|
32
|
+
const onSubmit = async (data: Record<string, string>) => {
|
|
33
|
+
const response = await changePassword({
|
|
34
|
+
currentPassword: data.current${passwordPascalCase},
|
|
35
|
+
newPassword: data.new${passwordPascalCase},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
if (response.error) {
|
|
39
|
+
toast.error(response.error)
|
|
40
|
+
} else {
|
|
41
|
+
// The function `changePassword.handler` in api/src/functions/auth.js
|
|
42
|
+
// decides if the user stays logged in. `reauthenticate()` picks up
|
|
43
|
+
// whichever it was
|
|
44
|
+
toast.success('${passwordTitleCase} changed!')
|
|
45
|
+
await reauthenticate()
|
|
46
|
+
navigate(routes.home())
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<Metadata title="Change ${passwordTitleCase}" />
|
|
53
|
+
|
|
54
|
+
<main className="rw-main">
|
|
55
|
+
<Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
|
|
56
|
+
<div className="rw-scaffold rw-login-container">
|
|
57
|
+
<div className="rw-segment">
|
|
58
|
+
<header className="rw-segment-header">
|
|
59
|
+
<h2 className="rw-heading rw-heading-secondary">
|
|
60
|
+
Change ${passwordTitleCase}
|
|
61
|
+
</h2>
|
|
62
|
+
</header>
|
|
63
|
+
|
|
64
|
+
<div className="rw-segment-main">
|
|
65
|
+
<div className="rw-form-wrapper">
|
|
66
|
+
<Form onSubmit={onSubmit} className="rw-form-wrapper">
|
|
67
|
+
<Label
|
|
68
|
+
name="current${passwordPascalCase}"
|
|
69
|
+
className="rw-label"
|
|
70
|
+
errorClassName="rw-label rw-label-error"
|
|
71
|
+
>
|
|
72
|
+
Current ${passwordTitleCase}
|
|
73
|
+
</Label>
|
|
74
|
+
<PasswordField
|
|
75
|
+
name="current${passwordPascalCase}"
|
|
76
|
+
autoComplete="current-password"
|
|
77
|
+
className="rw-input"
|
|
78
|
+
errorClassName="rw-input rw-input-error"
|
|
79
|
+
ref={current${passwordPascalCase}Ref}
|
|
80
|
+
validation={{
|
|
81
|
+
required: {
|
|
82
|
+
value: true,
|
|
83
|
+
message: 'Current ${passwordTitleCase} is required',
|
|
84
|
+
},
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<FieldError
|
|
89
|
+
name="current${passwordPascalCase}"
|
|
90
|
+
className="rw-field-error"
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
<Label
|
|
94
|
+
name="new${passwordPascalCase}"
|
|
95
|
+
className="rw-label"
|
|
96
|
+
errorClassName="rw-label rw-label-error"
|
|
97
|
+
>
|
|
98
|
+
New ${passwordTitleCase}
|
|
99
|
+
</Label>
|
|
100
|
+
<PasswordField
|
|
101
|
+
name="new${passwordPascalCase}"
|
|
102
|
+
autoComplete="new-password"
|
|
103
|
+
className="rw-input"
|
|
104
|
+
errorClassName="rw-input rw-input-error"
|
|
105
|
+
validation={{
|
|
106
|
+
required: {
|
|
107
|
+
value: true,
|
|
108
|
+
message: 'New ${passwordTitleCase} is required',
|
|
109
|
+
},
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
|
|
113
|
+
<FieldError
|
|
114
|
+
name="new${passwordPascalCase}"
|
|
115
|
+
className="rw-field-error"
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<div className="rw-button-group">
|
|
119
|
+
<Submit className="rw-button rw-button-blue">Submit</Submit>
|
|
120
|
+
</div>
|
|
121
|
+
</Form>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</main>
|
|
127
|
+
</>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default ChangePasswordPage
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createBuilder,
|
|
3
3
|
createCommand,
|
|
4
|
-
createHandler
|
|
4
|
+
createHandler,
|
|
5
|
+
getYargsDefaults
|
|
5
6
|
} from "../yargsCommandHelpers.js";
|
|
6
7
|
const command = createCommand("directive");
|
|
7
8
|
const description = "Generate a new GraphQL directive";
|
|
8
|
-
const builder = createBuilder({
|
|
9
|
+
const builder = createBuilder({
|
|
10
|
+
componentName: "directive",
|
|
11
|
+
optionsObj: () => ({
|
|
12
|
+
...getYargsDefaults(),
|
|
13
|
+
// No default value, so the handler prompts for a type when the option is
|
|
14
|
+
// omitted
|
|
15
|
+
type: {
|
|
16
|
+
description: "Directive type. If omitted, the generator prompts for one",
|
|
17
|
+
type: "string",
|
|
18
|
+
choices: ["validator", "transformer"]
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
});
|
|
9
22
|
const handler = createHandler("directive");
|
|
10
23
|
export {
|
|
11
24
|
builder,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBuiltin } from "node:module";
|
|
1
2
|
const START_LINE = /^([ \t]*)await server\.start\(\)/m;
|
|
2
3
|
const FACTORY_IMPORT = /import\s*\{[^}]*\bcreateAuthDecoder\b[^}]*\}\s*from\s*['"](@cedarjs\/auth-[a-z0-9-]+-api)['"]/;
|
|
3
4
|
const DECODER_IMPORT = /import\s*\{[^}]*\bauthDecoder\b[^}]*\}\s*from\s*['"](@cedarjs\/auth-[a-z0-9-]+-api)['"]/;
|
|
@@ -48,21 +49,20 @@ function hasBinding(source, name) {
|
|
|
48
49
|
return importedBindings(source).has(name) || declared.test(source);
|
|
49
50
|
}
|
|
50
51
|
function uploadsServerImports(source, auth) {
|
|
51
|
-
const
|
|
52
|
-
const srcSpecs = [];
|
|
52
|
+
const specs = [];
|
|
53
53
|
if (auth) {
|
|
54
54
|
if (auth.usesFactory) {
|
|
55
55
|
if (!hasBinding(source, "authDecoder")) {
|
|
56
|
-
|
|
56
|
+
specs.push({
|
|
57
57
|
names: ["createAuthDecoder"],
|
|
58
58
|
from: auth.decoderPackage
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
} else {
|
|
62
|
-
|
|
62
|
+
specs.push({ names: ["authDecoder"], from: auth.decoderPackage });
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
specs.push({
|
|
66
66
|
names: auth ? ["cedarUploadsPlugin", "createUploadAuthenticator"] : ["cedarUploadsPlugin"],
|
|
67
67
|
from: "@cedarjs/uploads"
|
|
68
68
|
});
|
|
@@ -71,20 +71,97 @@ function uploadsServerImports(source, auth) {
|
|
|
71
71
|
if (auth.usesFactory && !hasBinding(source, "authDecoder")) {
|
|
72
72
|
authNames.unshift("cookieName");
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
specs.push({ names: authNames, from: "src/lib/auth" });
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
specs.push({ names: ["db"], from: "src/lib/db" });
|
|
77
|
+
specs.push({ names: ["targets"], from: "src/lib/uploads" });
|
|
78
|
+
return specs.map((spec) => ({
|
|
79
79
|
...spec,
|
|
80
80
|
names: spec.names.filter((name) => !hasBinding(source, name))
|
|
81
|
-
})).filter((spec) => spec.names.length > 0).map((spec) =>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
})).filter((spec) => spec.names.length > 0).map((spec) => ({
|
|
82
|
+
line: `import { ${spec.names.join(", ")} } from '${spec.from}'`,
|
|
83
|
+
from: spec.from
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
function findImports(lines) {
|
|
87
|
+
const imports = [];
|
|
88
|
+
for (let start = 0; start < lines.length; start++) {
|
|
89
|
+
if (!/^import\b/.test(lines[start])) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const sideEffect = /^import\s*['"]([^'"]+)['"]/.exec(lines[start]);
|
|
93
|
+
if (sideEffect) {
|
|
94
|
+
imports.push({ start, end: start, from: sideEffect[1] });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
for (let end = start; end < lines.length; end++) {
|
|
98
|
+
const from = /\bfrom\s*['"]([^'"]+)['"]/.exec(lines[end]);
|
|
99
|
+
if (from) {
|
|
100
|
+
imports.push({ start, end, from: from[1] });
|
|
101
|
+
start = end;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return imports;
|
|
107
|
+
}
|
|
108
|
+
function importGroup(from) {
|
|
109
|
+
if (isBuiltin(from)) {
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
if (from === "react") {
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
if (from.startsWith("@cedarjs/")) {
|
|
116
|
+
return 3;
|
|
117
|
+
}
|
|
118
|
+
if (from.startsWith("src/")) {
|
|
119
|
+
return /^src\/[^/]+\/.+\.(?:sdl\.)?(?:js|ts)$/.test(from) ? 4 : 5;
|
|
86
120
|
}
|
|
87
|
-
|
|
121
|
+
if (from === ".." || from.startsWith("../")) {
|
|
122
|
+
return 6;
|
|
123
|
+
}
|
|
124
|
+
if (/^\.\/?(?:index(?:\.[jt]sx?)?)?$/.test(from)) {
|
|
125
|
+
return 8;
|
|
126
|
+
}
|
|
127
|
+
if (from.startsWith("./")) {
|
|
128
|
+
return 7;
|
|
129
|
+
}
|
|
130
|
+
return 2;
|
|
131
|
+
}
|
|
132
|
+
function compareSpecifiers(a, b) {
|
|
133
|
+
const segmentsA = a.toLowerCase().split("/");
|
|
134
|
+
const segmentsB = b.toLowerCase().split("/");
|
|
135
|
+
for (let i = 0; i < Math.min(segmentsA.length, segmentsB.length); i++) {
|
|
136
|
+
if (segmentsA[i] !== segmentsB[i]) {
|
|
137
|
+
return segmentsA[i] < segmentsB[i] ? -1 : 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return segmentsA.length - segmentsB.length;
|
|
141
|
+
}
|
|
142
|
+
function insertImport(lines, serverImport) {
|
|
143
|
+
const imports = findImports(lines);
|
|
144
|
+
const group = importGroup(serverImport.from);
|
|
145
|
+
const sameGroup = imports.filter((i) => importGroup(i.from) === group);
|
|
146
|
+
if (sameGroup.length > 0) {
|
|
147
|
+
const next = sameGroup.find(
|
|
148
|
+
(i) => compareSpecifiers(i.from, serverImport.from) > 0
|
|
149
|
+
);
|
|
150
|
+
const at2 = next ? next.start : sameGroup[sameGroup.length - 1].end + 1;
|
|
151
|
+
return [...lines.slice(0, at2), serverImport.line, ...lines.slice(at2)];
|
|
152
|
+
}
|
|
153
|
+
const earlier = imports.filter((i) => importGroup(i.from) < group);
|
|
154
|
+
if (earlier.length > 0) {
|
|
155
|
+
const at2 = Math.max(...earlier.map((i) => i.end)) + 1;
|
|
156
|
+
const inserted = ["", serverImport.line];
|
|
157
|
+
if (at2 < lines.length && lines[at2].trim() !== "") {
|
|
158
|
+
inserted.push("");
|
|
159
|
+
}
|
|
160
|
+
return [...lines.slice(0, at2), ...inserted, ...lines.slice(at2)];
|
|
161
|
+
}
|
|
162
|
+
const later = imports.find((i) => importGroup(i.from) > group);
|
|
163
|
+
const at = later ? later.start : 0;
|
|
164
|
+
return [...lines.slice(0, at), serverImport.line, "", ...lines.slice(at)];
|
|
88
165
|
}
|
|
89
166
|
function uploadsServerRegistration(source, auth) {
|
|
90
167
|
if (!auth) {
|
|
@@ -122,18 +199,7 @@ function addUploadsPlugin(source, { auth = null } = {}) {
|
|
|
122
199
|
if (!start) {
|
|
123
200
|
throw new Error("CEDAR_UPLOADS_ERR_NO_START");
|
|
124
201
|
}
|
|
125
|
-
const
|
|
126
|
-
let lastImport = -1;
|
|
127
|
-
for (const [index, line] of lines.entries()) {
|
|
128
|
-
if (/^import\s/.test(line)) {
|
|
129
|
-
lastImport = index;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const withImports = [
|
|
133
|
-
...lines.slice(0, lastImport + 1),
|
|
134
|
-
...uploadsServerImports(source, auth),
|
|
135
|
-
...lines.slice(lastImport + 1)
|
|
136
|
-
].join("\n");
|
|
202
|
+
const withImports = uploadsServerImports(source, auth).reduce(insertImport, source.split("\n")).join("\n");
|
|
137
203
|
return withImports.replace(
|
|
138
204
|
START_LINE,
|
|
139
205
|
(line) => `${uploadsServerRegistration(source, auth)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/cli",
|
|
3
|
-
"version": "8.0.0-canary.
|
|
3
|
+
"version": "8.0.0-canary.3285",
|
|
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.8",
|
|
38
38
|
"@babel/types": "7.29.8",
|
|
39
|
-
"@cedarjs/api-server": "8.0.0-canary.
|
|
40
|
-
"@cedarjs/babel-config": "8.0.0-canary.
|
|
41
|
-
"@cedarjs/cli-helpers": "8.0.0-canary.
|
|
42
|
-
"@cedarjs/internal": "8.0.0-canary.
|
|
43
|
-
"@cedarjs/prerender": "8.0.0-canary.
|
|
44
|
-
"@cedarjs/project-config": "8.0.0-canary.
|
|
45
|
-
"@cedarjs/structure": "8.0.0-canary.
|
|
46
|
-
"@cedarjs/telemetry": "8.0.0-canary.
|
|
47
|
-
"@cedarjs/utils": "8.0.0-canary.
|
|
48
|
-
"@cedarjs/vite": "8.0.0-canary.
|
|
49
|
-
"@cedarjs/web-server": "8.0.0-canary.
|
|
39
|
+
"@cedarjs/api-server": "8.0.0-canary.3285",
|
|
40
|
+
"@cedarjs/babel-config": "8.0.0-canary.3285",
|
|
41
|
+
"@cedarjs/cli-helpers": "8.0.0-canary.3285",
|
|
42
|
+
"@cedarjs/internal": "8.0.0-canary.3285",
|
|
43
|
+
"@cedarjs/prerender": "8.0.0-canary.3285",
|
|
44
|
+
"@cedarjs/project-config": "8.0.0-canary.3285",
|
|
45
|
+
"@cedarjs/structure": "8.0.0-canary.3285",
|
|
46
|
+
"@cedarjs/telemetry": "8.0.0-canary.3285",
|
|
47
|
+
"@cedarjs/utils": "8.0.0-canary.3285",
|
|
48
|
+
"@cedarjs/vite": "8.0.0-canary.3285",
|
|
49
|
+
"@cedarjs/web-server": "8.0.0-canary.3285",
|
|
50
50
|
"@listr2/prompt-adapter-enquirer": "4.3.2",
|
|
51
51
|
"@opentelemetry/api": "1.9.1",
|
|
52
52
|
"@opentelemetry/core": "2.11.0",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"yargs": "17.7.3"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
97
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3285",
|
|
98
98
|
"@prisma/dmmf": "7.10.0",
|
|
99
99
|
"@types/archiver": "^7.0.0",
|
|
100
100
|
"memfs": "4.79.0",
|