@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
|
@@ -10,38 +10,136 @@ import {
|
|
|
10
10
|
getPaths,
|
|
11
11
|
printSetupNotes
|
|
12
12
|
} from "../../../../lib/index.js";
|
|
13
|
+
import { MONITORS, isMonitor } from "../../../deploy/baremetal/monitors.js";
|
|
13
14
|
import { addFilesTask } from "../helpers/index.js";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
MAINTENANCE,
|
|
17
|
+
SYSTEMD_DIR,
|
|
18
|
+
deployToml,
|
|
19
|
+
ecosystemConfig,
|
|
20
|
+
systemdJobsUnit,
|
|
21
|
+
systemdServiceUnit
|
|
22
|
+
} from "../templates/baremetal.js";
|
|
15
23
|
const configFilename = "deploy.toml";
|
|
16
|
-
const
|
|
24
|
+
const MONITOR_CHOICES = [
|
|
17
25
|
{
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
title: "pm2",
|
|
27
|
+
description: "Node process manager, configured in ecosystem.config.js",
|
|
28
|
+
value: "pm2"
|
|
20
29
|
},
|
|
21
30
|
{
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
title: "systemd (user units)",
|
|
32
|
+
description: "Units in ~/.config/systemd/user, managed by the deploy user without sudo",
|
|
33
|
+
value: "systemd-user"
|
|
24
34
|
},
|
|
25
35
|
{
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
title: "systemd (system units)",
|
|
37
|
+
description: "Units in /etc/systemd/system, managed with sudo systemctl",
|
|
38
|
+
value: "systemd-system"
|
|
28
39
|
}
|
|
29
40
|
];
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const appNameFromPath = (projectPath) => {
|
|
42
|
+
const name = path.basename(projectPath).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
43
|
+
return name || "cedar";
|
|
44
|
+
};
|
|
45
|
+
const setupFiles = ({
|
|
46
|
+
monitor,
|
|
47
|
+
jobs,
|
|
48
|
+
appName,
|
|
49
|
+
basePath,
|
|
50
|
+
webSrcPath
|
|
51
|
+
}) => {
|
|
52
|
+
const files = [];
|
|
53
|
+
let processNames;
|
|
54
|
+
if (monitor === "pm2") {
|
|
55
|
+
processNames = jobs ? ["serve", "jobs"] : ["serve"];
|
|
56
|
+
files.push({
|
|
57
|
+
path: path.join(basePath, "ecosystem.config.js"),
|
|
58
|
+
content: ecosystemConfig({ jobs })
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
const jobsUnit = `${appName}-jobs`;
|
|
62
|
+
processNames = jobs ? [appName, `${jobsUnit}@0`] : [appName];
|
|
63
|
+
files.push({
|
|
64
|
+
path: path.join(basePath, SYSTEMD_DIR, `${appName}.service`),
|
|
65
|
+
content: systemdServiceUnit({ appName, monitor })
|
|
66
|
+
});
|
|
67
|
+
if (jobs) {
|
|
68
|
+
files.push({
|
|
69
|
+
path: path.join(basePath, SYSTEMD_DIR, `${jobsUnit}@.service`),
|
|
70
|
+
content: systemdJobsUnit({ appName, monitor })
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
files.unshift({
|
|
75
|
+
path: path.join(basePath, configFilename),
|
|
76
|
+
content: deployToml({ monitor, processNames })
|
|
77
|
+
});
|
|
78
|
+
files.push({
|
|
79
|
+
path: path.join(webSrcPath, "maintenance.html"),
|
|
80
|
+
content: MAINTENANCE
|
|
81
|
+
});
|
|
82
|
+
return { files, processNames };
|
|
83
|
+
};
|
|
84
|
+
const notesFor = (monitor, jobs, processNames) => {
|
|
85
|
+
const notes = ["You are almost ready to go BAREMETAL!", ""];
|
|
86
|
+
if (monitor !== "pm2") {
|
|
87
|
+
notes.push(
|
|
88
|
+
`Copy the unit files in ${SYSTEMD_DIR}/ to the server before your first`,
|
|
89
|
+
"deploy. The comments at the top of each file say where they go.",
|
|
90
|
+
""
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (jobs) {
|
|
94
|
+
notes.push(
|
|
95
|
+
`Background jobs are set up, so deploy.toml lists a jobs worker process`,
|
|
96
|
+
`(${processNames[processNames.length - 1]}). Add one per entry in the`,
|
|
97
|
+
"`workers` array of api/src/lib/jobs.ts.",
|
|
98
|
+
""
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
notes.push(
|
|
102
|
+
"See https://cedarjs.com/docs/deploy/baremetal for the remaining",
|
|
103
|
+
"config and setup required before you can perform your first deploy."
|
|
104
|
+
);
|
|
105
|
+
return notes;
|
|
106
|
+
};
|
|
107
|
+
const promptForMonitor = async () => {
|
|
108
|
+
const { monitor } = await prompts({
|
|
109
|
+
type: "select",
|
|
110
|
+
name: "monitor",
|
|
111
|
+
message: "Which process monitor will run your app on the server?",
|
|
112
|
+
choices: MONITOR_CHOICES
|
|
113
|
+
});
|
|
114
|
+
return isMonitor(monitor) ? monitor : void 0;
|
|
115
|
+
};
|
|
116
|
+
const handler = async ({
|
|
117
|
+
force,
|
|
118
|
+
monitor: monitorOption
|
|
119
|
+
}) => {
|
|
37
120
|
recordTelemetryAttributes({
|
|
38
121
|
command: "setup deploy baremetal",
|
|
39
|
-
force
|
|
122
|
+
force,
|
|
123
|
+
monitor: monitorOption
|
|
40
124
|
});
|
|
41
|
-
if (
|
|
125
|
+
if (monitorOption !== void 0 && !isMonitor(monitorOption)) {
|
|
126
|
+
console.error(
|
|
127
|
+
c.error(
|
|
128
|
+
`Unknown monitor "${monitorOption}". Use one of ${MONITORS.join(", ")}.`
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const monitor = monitorOption ?? await promptForMonitor();
|
|
134
|
+
if (!monitor) {
|
|
135
|
+
console.log("Aborting baremetal setup.");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const paths = getPaths();
|
|
139
|
+
if (fs.existsSync(path.join(paths.base, ".pnp.cjs"))) {
|
|
42
140
|
console.warn(
|
|
43
141
|
c.warning(
|
|
44
|
-
"Your project uses Yarn PnP (Plug'n'Play), which is not officially supported for Baremetal deployments. The generated
|
|
142
|
+
"Your project uses Yarn PnP (Plug'n'Play), which is not officially supported for Baremetal deployments. The generated process config starts the app with node_modules/.bin/cedar, which will most likely not work under PnP.\n\nYou will need to manually configure the server. See also the packageManagerCommand field in deploy.toml."
|
|
45
143
|
)
|
|
46
144
|
);
|
|
47
145
|
console.log();
|
|
@@ -56,6 +154,14 @@ const handler = async ({ force }) => {
|
|
|
56
154
|
}
|
|
57
155
|
console.log();
|
|
58
156
|
}
|
|
157
|
+
const jobs = Boolean(paths.api.jobsConfig);
|
|
158
|
+
const { files, processNames } = setupFiles({
|
|
159
|
+
monitor,
|
|
160
|
+
jobs,
|
|
161
|
+
appName: appNameFromPath(paths.base),
|
|
162
|
+
basePath: paths.base,
|
|
163
|
+
webSrcPath: paths.web.src
|
|
164
|
+
});
|
|
59
165
|
const tasks = new Listr(
|
|
60
166
|
[
|
|
61
167
|
await addPackagesTask({
|
|
@@ -66,7 +172,7 @@ const handler = async ({ force }) => {
|
|
|
66
172
|
files,
|
|
67
173
|
force
|
|
68
174
|
}),
|
|
69
|
-
printSetupNotes(
|
|
175
|
+
printSetupNotes(notesFor(monitor, jobs, processNames))
|
|
70
176
|
],
|
|
71
177
|
{ rendererOptions: { collapseSubtasks: false } }
|
|
72
178
|
);
|
|
@@ -81,6 +187,8 @@ const handler = async ({ force }) => {
|
|
|
81
187
|
}
|
|
82
188
|
};
|
|
83
189
|
export {
|
|
190
|
+
appNameFromPath,
|
|
84
191
|
configFilename,
|
|
85
|
-
handler
|
|
192
|
+
handler,
|
|
193
|
+
setupFiles
|
|
86
194
|
};
|
|
@@ -56,11 +56,17 @@ function addNetlifyPluginsToViteConfigTask() {
|
|
|
56
56
|
const hasNetlifyCompat = content.includes(
|
|
57
57
|
"@universal-deploy/netlify/vite"
|
|
58
58
|
);
|
|
59
|
-
|
|
59
|
+
const hasSkewProtectionImport = content.includes(
|
|
60
|
+
"cedarNetlifySkewProtectionPlugin"
|
|
61
|
+
);
|
|
62
|
+
const hasSkewProtectionCall = content.includes(
|
|
63
|
+
"cedarNetlifySkewProtectionPlugin("
|
|
64
|
+
);
|
|
65
|
+
if (hasNetlifyPlugin && hasNetlifyCompat && hasSkewProtectionImport && hasSkewProtectionCall && content.includes("netlifyCompat(")) {
|
|
60
66
|
task.skip("Netlify plugins are already configured.");
|
|
61
67
|
return;
|
|
62
68
|
}
|
|
63
|
-
if (!hasNetlifyPlugin || !hasNetlifyCompat) {
|
|
69
|
+
if (!hasNetlifyPlugin || !hasNetlifyCompat || !hasSkewProtectionImport) {
|
|
64
70
|
const newContent = content.replace(
|
|
65
71
|
/(import\s+\{[^}]*\}\s+from\s+['"]vite['"];?)/,
|
|
66
72
|
(match) => {
|
|
@@ -71,6 +77,10 @@ ${result}`;
|
|
|
71
77
|
}
|
|
72
78
|
if (!hasNetlifyCompat) {
|
|
73
79
|
result = `import netlifyCompat from '@universal-deploy/netlify/vite'
|
|
80
|
+
${result}`;
|
|
81
|
+
}
|
|
82
|
+
if (!hasSkewProtectionImport) {
|
|
83
|
+
result = `import { cedarNetlifySkewProtectionPlugin } from '@cedarjs/vite'
|
|
74
84
|
${result}`;
|
|
75
85
|
}
|
|
76
86
|
return result;
|
|
@@ -84,6 +94,9 @@ ${result}`;
|
|
|
84
94
|
if (!hasNetlifyCompat) {
|
|
85
95
|
prepend += "import netlifyCompat from '@universal-deploy/netlify/vite'\n";
|
|
86
96
|
}
|
|
97
|
+
if (!hasSkewProtectionImport) {
|
|
98
|
+
prepend += "import { cedarNetlifySkewProtectionPlugin } from '@cedarjs/vite'\n";
|
|
99
|
+
}
|
|
87
100
|
content = prepend + content;
|
|
88
101
|
} else {
|
|
89
102
|
content = newContent;
|
|
@@ -94,12 +107,21 @@ ${result}`;
|
|
|
94
107
|
content,
|
|
95
108
|
pluginCodes: [
|
|
96
109
|
"netlify({ build: { enabled: true } })",
|
|
97
|
-
"netlifyCompat()"
|
|
110
|
+
"netlifyCompat()",
|
|
111
|
+
"cedarNetlifySkewProtectionPlugin()"
|
|
98
112
|
]
|
|
99
113
|
});
|
|
100
114
|
if (result) {
|
|
101
115
|
content = result;
|
|
102
116
|
}
|
|
117
|
+
} else if (!hasSkewProtectionCall) {
|
|
118
|
+
const result = insertPluginsBeforeCedar({
|
|
119
|
+
content,
|
|
120
|
+
pluginCodes: ["cedarNetlifySkewProtectionPlugin()"]
|
|
121
|
+
});
|
|
122
|
+
if (result) {
|
|
123
|
+
content = result;
|
|
124
|
+
}
|
|
103
125
|
}
|
|
104
126
|
fs.writeFileSync(viteConfigPath, content);
|
|
105
127
|
}
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import { getPackageManager } from "@cedarjs/project-config/packageManager";
|
|
2
|
-
const
|
|
2
|
+
const SYSTEMD_DIR = "systemd";
|
|
3
|
+
const JOBS_WORKER_BIN = "api/node_modules/.bin/cedar-jobs-worker";
|
|
4
|
+
const WORKER_STOP_TIMEOUT_SECONDS = 600;
|
|
5
|
+
const JOBS_WORKER_COMMENT = `# Runs one background jobs worker. \`--index\` picks the entry of the \`workers\`
|
|
6
|
+
# array in api/src/lib/jobs.ts and \`--id\` numbers the worker within that
|
|
7
|
+
# entry, so an entry with \`count: 2\` needs a second worker with \`--id=1\`.`;
|
|
8
|
+
const ecosystemConfig = ({ jobs }) => {
|
|
9
|
+
const jobsApp = jobs ? `
|
|
10
|
+
{
|
|
11
|
+
// Runs one background jobs worker. \`--index\` picks the entry of the
|
|
12
|
+
// \`workers\` array in api/src/lib/jobs.ts and \`--id\` numbers the worker
|
|
13
|
+
// within that entry, so an entry with \`count: 2\` needs a second app
|
|
14
|
+
// with \`--id=1\`.
|
|
15
|
+
name: 'jobs',
|
|
16
|
+
cwd: 'current',
|
|
17
|
+
script: '${JOBS_WORKER_BIN}',
|
|
18
|
+
args: '--index=0 --id=0',
|
|
19
|
+
// pm2 stops the worker with SIGINT, which lets it finish the job it is
|
|
20
|
+
// running. This is how long pm2 waits for that (in ms) before killing it.
|
|
21
|
+
kill_timeout: ${WORKER_STOP_TIMEOUT_SECONDS * 1e3},
|
|
22
|
+
},` : "";
|
|
23
|
+
return `module.exports = {
|
|
3
24
|
apps: [
|
|
4
25
|
{
|
|
5
26
|
name: 'serve',
|
|
@@ -10,10 +31,79 @@ const ECOSYSTEM = `module.exports = {
|
|
|
10
31
|
exec_mode: 'cluster',
|
|
11
32
|
wait_ready: true,
|
|
12
33
|
listen_timeout: 10000,
|
|
13
|
-
}
|
|
34
|
+
},${jobsApp}
|
|
14
35
|
],
|
|
15
|
-
}
|
|
16
|
-
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
};
|
|
39
|
+
const unitHeader = (appName, monitor) => {
|
|
40
|
+
const install = monitor === "systemd-system" ? `# sudo cp ${SYSTEMD_DIR}/*.service /etc/systemd/system/` : `# mkdir -p ~/.config/systemd/user && cp ${SYSTEMD_DIR}/*.service ~/.config/systemd/user/`;
|
|
41
|
+
const linger = monitor === "systemd-system" ? "" : `#
|
|
42
|
+
# User units only run while the user has a session, so let them run after a
|
|
43
|
+
# reboot too:
|
|
44
|
+
# sudo loginctl enable-linger $(whoami)
|
|
45
|
+
`;
|
|
46
|
+
return `# systemd unit for the ${appName} Cedar app, generated by \`cedar setup deploy baremetal\`.
|
|
47
|
+
#
|
|
48
|
+
# Copy the unit files to the server once, before the first deploy:
|
|
49
|
+
${install}
|
|
50
|
+
${linger}#
|
|
51
|
+
# Then deploy with \`--first-run\`, which reloads the unit files and enables and
|
|
52
|
+
# starts every process listed in deploy.toml.
|
|
53
|
+
#
|
|
54
|
+
# WorkingDirectory must be the \`path\` from deploy.toml followed by \`/current\`.`;
|
|
55
|
+
};
|
|
56
|
+
const unitService = (monitor, execStart, extraDirectives = "") => {
|
|
57
|
+
const user = monitor === "systemd-system" ? "\nUser=deploy" : "";
|
|
58
|
+
return `[Service]
|
|
59
|
+
WorkingDirectory=/var/www/app/current
|
|
60
|
+
# A login shell puts node and ${getPackageManager()} from a version manager such as nvm on the PATH
|
|
61
|
+
ExecStart=/bin/bash -lc '${execStart}'
|
|
62
|
+
Restart=always
|
|
63
|
+
RestartSec=5
|
|
64
|
+
Environment=NODE_ENV=production${user}${extraDirectives}`;
|
|
65
|
+
};
|
|
66
|
+
const JOBS_STOP_DIRECTIVES = `
|
|
67
|
+
# Stopping sends SIGINT so the worker finishes the job it is running before it
|
|
68
|
+
# exits. systemd waits this long for that before killing the worker.
|
|
69
|
+
KillSignal=SIGINT
|
|
70
|
+
TimeoutStopSec=${WORKER_STOP_TIMEOUT_SECONDS}`;
|
|
71
|
+
const unitInstall = (monitor) => `[Install]
|
|
72
|
+
WantedBy=${monitor === "systemd-system" ? "multi-user.target" : "default.target"}`;
|
|
73
|
+
const systemdServiceUnit = ({
|
|
74
|
+
appName,
|
|
75
|
+
monitor
|
|
76
|
+
}) => `${unitHeader(appName, monitor)}
|
|
77
|
+
|
|
78
|
+
[Unit]
|
|
79
|
+
Description=${appName} (Cedar)
|
|
80
|
+
After=network.target
|
|
81
|
+
|
|
82
|
+
${unitService(monitor, "exec node_modules/.bin/cedar serve")}
|
|
83
|
+
|
|
84
|
+
${unitInstall(monitor)}
|
|
85
|
+
`;
|
|
86
|
+
const systemdJobsUnit = ({
|
|
87
|
+
appName,
|
|
88
|
+
monitor
|
|
89
|
+
}) => `${unitHeader(appName, monitor)}
|
|
90
|
+
#
|
|
91
|
+
${JOBS_WORKER_COMMENT}
|
|
92
|
+
# Start one instance per \`workers\` entry: \`${appName}-jobs@0\`, \`${appName}-jobs@1\`
|
|
93
|
+
# and so on, and list each one in \`processNames\` in deploy.toml.
|
|
94
|
+
|
|
95
|
+
[Unit]
|
|
96
|
+
Description=${appName} background jobs worker %i (Cedar)
|
|
97
|
+
After=network.target
|
|
98
|
+
|
|
99
|
+
${unitService(monitor, `exec ${JOBS_WORKER_BIN} --index=%i --id=0`, JOBS_STOP_DIRECTIVES)}
|
|
100
|
+
|
|
101
|
+
${unitInstall(monitor)}
|
|
102
|
+
`;
|
|
103
|
+
const deployToml = ({
|
|
104
|
+
monitor,
|
|
105
|
+
processNames
|
|
106
|
+
}) => `# This file contains config for a baremetal deployment
|
|
17
107
|
#
|
|
18
108
|
# SSH connection options include:
|
|
19
109
|
#
|
|
@@ -26,6 +116,22 @@ const DEPLOY = `# This file contains config for a baremetal deployment
|
|
|
26
116
|
# * passphrase - used if your private key has a passphrase
|
|
27
117
|
# * agentForward - set to \`true\` to forward the client machine's ssh credentials
|
|
28
118
|
#
|
|
119
|
+
# Process monitor options:
|
|
120
|
+
#
|
|
121
|
+
# * monitor - "pm2", "systemd-user" or "systemd-system". Decides how processes
|
|
122
|
+
# are started for the first time, restarted and stopped
|
|
123
|
+
# * monitorCommand - overrides the command the monitor is invoked with. Defaults
|
|
124
|
+
# to \`pm2\`, \`systemctl --user\` or \`sudo systemctl\` depending on \`monitor\`,
|
|
125
|
+
# for example \`doppler run -- pm2\`
|
|
126
|
+
# * processNames - the pm2 app names or systemd unit names to (re)start
|
|
127
|
+
#
|
|
128
|
+
# After restarting the processes, the deploy requests the api side's health
|
|
129
|
+
# check endpoint on the server and fails if it doesn't respond in time:
|
|
130
|
+
#
|
|
131
|
+
# * healthCheckUrl - defaults to http://localhost:<[api].port>/graphql/health
|
|
132
|
+
# on servers that host the api side. Set to \`false\` to skip the check
|
|
133
|
+
# * healthCheckTimeout - seconds to keep retrying, defaults to 30
|
|
134
|
+
#
|
|
29
135
|
# See https://cedarjs.com/docs/deploy/baremetal for more info
|
|
30
136
|
|
|
31
137
|
[[production.servers]]
|
|
@@ -34,13 +140,14 @@ username = "user"
|
|
|
34
140
|
agentForward = true
|
|
35
141
|
sides = ["api","web"]
|
|
36
142
|
packageManagerCommand = "${getPackageManager()}"
|
|
37
|
-
|
|
143
|
+
monitor = "${monitor}"
|
|
38
144
|
path = "/var/www/app"
|
|
39
|
-
processNames = ["
|
|
145
|
+
processNames = [${processNames.map((name) => `"${name}"`).join(", ")}]
|
|
40
146
|
repo = "git@github.com:myorg/myapp.git"
|
|
41
147
|
branch = "main"
|
|
42
148
|
keepReleases = 5
|
|
43
149
|
freeSpaceRequired = 2048
|
|
150
|
+
healthCheckTimeout = 30
|
|
44
151
|
|
|
45
152
|
# If you have separate api and web servers:
|
|
46
153
|
#
|
|
@@ -123,7 +230,10 @@ And take it back down with:
|
|
|
123
230
|
</html>
|
|
124
231
|
`;
|
|
125
232
|
export {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
233
|
+
MAINTENANCE,
|
|
234
|
+
SYSTEMD_DIR,
|
|
235
|
+
deployToml,
|
|
236
|
+
ecosystemConfig,
|
|
237
|
+
systemdJobsUnit,
|
|
238
|
+
systemdServiceUnit
|
|
129
239
|
};
|
|
@@ -10,6 +10,9 @@ NODE_VERSION = "24"
|
|
|
10
10
|
|
|
11
11
|
[functions]
|
|
12
12
|
directory = "api/dist/ud"
|
|
13
|
+
# Sandboxed child scripts (\`@cedarjs/api/sandbox\`) and the packages they
|
|
14
|
+
# import. Nothing imports them, so Netlify's file tracing can't find them
|
|
15
|
+
included_files = ["api/dist/ud/sandbox-children/**"]
|
|
13
16
|
|
|
14
17
|
# To use Netlify Dev, install Netlify's CLI (\`netlify-cli\`) from NPM and use
|
|
15
18
|
# \`netlify link\` to connect your local project to a site on Netlify. Then run
|
|
@@ -76,7 +76,6 @@ const handler = async ({ force }) => {
|
|
|
76
76
|
"export const handler = createGraphQLHandler({",
|
|
77
77
|
"extraPlugins: [useSentry({",
|
|
78
78
|
" includeRawResult: true,",
|
|
79
|
-
" includeResolverArgs: true,",
|
|
80
79
|
" includeExecuteVariables: true,",
|
|
81
80
|
"})],"
|
|
82
81
|
);
|
|
@@ -112,7 +111,11 @@ const handler = async ({ force }) => {
|
|
|
112
111
|
contentLines.splice(
|
|
113
112
|
boundaryOpenIndex,
|
|
114
113
|
1,
|
|
115
|
-
"<Sentry.ErrorBoundary
|
|
114
|
+
"<Sentry.ErrorBoundary",
|
|
115
|
+
" fallback={({ error }) => (",
|
|
116
|
+
" <FatalErrorPage error={error instanceof Error ? error : undefined} />",
|
|
117
|
+
" )}",
|
|
118
|
+
">"
|
|
116
119
|
);
|
|
117
120
|
const boundaryCloseIndex = contentLines.findLastIndex(
|
|
118
121
|
(line) => line.includes("</FatalErrorBoundary>")
|
|
@@ -3,10 +3,7 @@ import * as Sentry from '@sentry/node'
|
|
|
3
3
|
Sentry.init({
|
|
4
4
|
dsn: process.env.SENTRY_DSN,
|
|
5
5
|
environment: process.env.NODE_ENV,
|
|
6
|
-
integrations: [
|
|
7
|
-
Sentry.prismaIntegration(),
|
|
8
|
-
Sentry.httpIntegration(),
|
|
9
|
-
],
|
|
6
|
+
integrations: [Sentry.prismaIntegration(), Sentry.httpIntegration()],
|
|
10
7
|
tracesSampleRate: 1.0,
|
|
11
8
|
})
|
|
12
9
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import * as Sentry from '@sentry/react'
|
|
2
2
|
|
|
3
|
+
const sentryDsn =
|
|
4
|
+
typeof process === 'undefined' ? undefined : process.env?.SENTRY_DSN
|
|
5
|
+
|
|
3
6
|
let dsn = ''
|
|
4
7
|
let environment = 'development'
|
|
5
8
|
|
|
6
|
-
if (
|
|
9
|
+
if (!sentryDsn) {
|
|
7
10
|
console.error(
|
|
8
11
|
'Missing SENTRY_DSN environment variable. Did you forget to add it to ' +
|
|
9
12
|
'your cedar.toml (or redwood.toml) in `includeEnvironmentVariables`?'
|
|
@@ -17,14 +20,14 @@ if (typeof process === 'undefined' || !process.env?.SENTRY_DSN) {
|
|
|
17
20
|
`)
|
|
18
21
|
console.error('Sentry is disabled for now')
|
|
19
22
|
} else {
|
|
20
|
-
dsn =
|
|
21
|
-
environment = process.env.NODE_ENV
|
|
23
|
+
dsn = sentryDsn
|
|
24
|
+
environment = process.env.NODE_ENV ?? 'development'
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
Sentry.init({
|
|
25
28
|
dsn,
|
|
26
29
|
environment,
|
|
27
|
-
integrations: [
|
|
30
|
+
integrations: [Sentry.browserTracingIntegration()],
|
|
28
31
|
tracesSampleRate: 1.0,
|
|
29
32
|
})
|
|
30
33
|
|
package/dist/commands/setup/realtime/templates/defer/fastAndSlowFields/fastAndSlowFields.ts.template
CHANGED
|
@@ -7,7 +7,10 @@ export const fastField = async () => {
|
|
|
7
7
|
return 'I am fast'
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export const slowField = async (
|
|
10
|
+
export const slowField = async (
|
|
11
|
+
_: unknown,
|
|
12
|
+
{ waitFor = 5000 }: { waitFor?: number }
|
|
13
|
+
) => {
|
|
11
14
|
logger.debug('waiting on slowField')
|
|
12
15
|
await wait(waitFor)
|
|
13
16
|
return 'I am slow'
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
import type { BidInput } from 'types/graphql'
|
|
2
|
+
|
|
1
3
|
import type { LiveQueryStorageMechanism } from '@cedarjs/realtime'
|
|
2
4
|
|
|
3
5
|
import { logger } from 'src/lib/logger'
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
interface AuctionData {
|
|
8
|
+
id: string
|
|
9
|
+
title: string
|
|
10
|
+
bids: { amount: number }[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const auctions: AuctionData[] = [
|
|
6
14
|
{ id: '1', title: 'RedwoodJS Logo Shirt', bids: [{ amount: 20 }] },
|
|
7
15
|
{ id: '2', title: 'RedwoodJS Lapel Pin', bids: [{ amount: 5 }] },
|
|
8
16
|
{ id: '3', title: 'RedwoodJS Beanie', bids: [{ amount: 15 }] },
|
|
@@ -34,14 +42,14 @@ const auctions = [
|
|
|
34
42
|
* }
|
|
35
43
|
* }
|
|
36
44
|
*/
|
|
37
|
-
export const auction = async ({ id }) => {
|
|
45
|
+
export const auction = async ({ id }: { id: string }) => {
|
|
38
46
|
const foundAuction = auctions.find((a) => a.id === id)
|
|
39
47
|
logger.debug({ id, auction: foundAuction }, 'auction')
|
|
40
48
|
return foundAuction
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
export const bid = async (
|
|
44
|
-
{ input },
|
|
52
|
+
{ input }: { input: BidInput },
|
|
45
53
|
{ context }: { context: { liveQueryStore: LiveQueryStorageMechanism } }
|
|
46
54
|
) => {
|
|
47
55
|
const { auctionId, amount } = input
|
|
@@ -62,7 +70,7 @@ export const bid = async (
|
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
export const Auction = {
|
|
65
|
-
highestBid: (obj, { root }) => {
|
|
73
|
+
highestBid: (obj: unknown, { root }: { root: AuctionData }) => {
|
|
66
74
|
const [max] = root.bids.sort((a, b) => b.amount - a.amount)
|
|
67
75
|
|
|
68
76
|
logger.debug({ obj, root }, 'highestBid')
|
|
@@ -52,7 +52,8 @@ function wrapCreateCall(fn) {
|
|
|
52
52
|
),
|
|
53
53
|
j.objectProperty(
|
|
54
54
|
j.identifier("invitationToken"),
|
|
55
|
-
|
|
55
|
+
// `userAttributes` is optional in dbAuth's signup handler options
|
|
56
|
+
j.optionalMemberExpression(
|
|
56
57
|
j.identifier("userAttributes"),
|
|
57
58
|
j.identifier("invitationToken")
|
|
58
59
|
)
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
|
|
3
|
+
import type {
|
|
4
|
+
AcceptInvitationMutation,
|
|
5
|
+
AcceptInvitationMutationVariables,
|
|
6
|
+
} from 'types/graphql'
|
|
7
|
+
|
|
3
8
|
import { navigate, routes, useParams } from '@cedarjs/router'
|
|
4
9
|
import { Metadata, useMutation } from '@cedarjs/web'
|
|
10
|
+
import type { TypedDocumentNode } from '@cedarjs/web'
|
|
5
11
|
|
|
6
12
|
import { useAuth } from 'src/auth'
|
|
7
13
|
|
|
8
|
-
const ACCEPT_INVITATION_MUTATION
|
|
14
|
+
const ACCEPT_INVITATION_MUTATION: TypedDocumentNode<
|
|
15
|
+
AcceptInvitationMutation,
|
|
16
|
+
AcceptInvitationMutationVariables
|
|
17
|
+
> = gql`
|
|
9
18
|
mutation AcceptInvitationMutation($token: String!) {
|
|
10
19
|
acceptInvitation(token: $token) {
|
|
11
20
|
id
|
|
@@ -26,7 +26,6 @@ export const standard = defineScenario<
|
|
|
26
26
|
email: 'owner@example.com',
|
|
27
27
|
hashedPassword: 'x',
|
|
28
28
|
salt: 'x',
|
|
29
|
-
fullName: 'Org Owner',
|
|
30
29
|
},
|
|
31
30
|
},
|
|
32
31
|
},
|
|
@@ -40,7 +39,6 @@ export const standard = defineScenario<
|
|
|
40
39
|
email: 'admin@example.com',
|
|
41
40
|
hashedPassword: 'x',
|
|
42
41
|
salt: 'x',
|
|
43
|
-
fullName: 'Org Admin',
|
|
44
42
|
},
|
|
45
43
|
},
|
|
46
44
|
},
|
|
@@ -199,7 +199,6 @@ describe('organizations service', () => {
|
|
|
199
199
|
email: 'second-owner@example.com',
|
|
200
200
|
hashedPassword: 'x',
|
|
201
201
|
salt: 'x',
|
|
202
|
-
fullName: 'Second Owner',
|
|
203
202
|
},
|
|
204
203
|
})
|
|
205
204
|
await db.membership.create({
|
|
@@ -233,7 +232,6 @@ describe('organizations service', () => {
|
|
|
233
232
|
email: 'invitee@example.com',
|
|
234
233
|
hashedPassword: 'x',
|
|
235
234
|
salt: 'x',
|
|
236
|
-
fullName: 'Invitee',
|
|
237
235
|
},
|
|
238
236
|
})
|
|
239
237
|
|
|
@@ -255,7 +253,6 @@ describe('organizations service', () => {
|
|
|
255
253
|
email: 'invitee@example.com',
|
|
256
254
|
hashedPassword: 'x',
|
|
257
255
|
salt: 'x',
|
|
258
|
-
fullName: 'Invitee',
|
|
259
256
|
},
|
|
260
257
|
})
|
|
261
258
|
|
|
@@ -278,7 +275,6 @@ describe('organizations service', () => {
|
|
|
278
275
|
email: 'first@example.com',
|
|
279
276
|
hashedPassword: 'x',
|
|
280
277
|
salt: 'x',
|
|
281
|
-
fullName: 'First User',
|
|
282
278
|
},
|
|
283
279
|
})
|
|
284
280
|
const secondUser = await db.user.create({
|
|
@@ -286,7 +282,6 @@ describe('organizations service', () => {
|
|
|
286
282
|
email: 'second@example.com',
|
|
287
283
|
hashedPassword: 'x',
|
|
288
284
|
salt: 'x',
|
|
289
|
-
fullName: 'Second User',
|
|
290
285
|
},
|
|
291
286
|
})
|
|
292
287
|
|
|
@@ -308,7 +303,6 @@ describe('organizations service', () => {
|
|
|
308
303
|
email: 'invitee@example.com',
|
|
309
304
|
hashedPassword: 'x',
|
|
310
305
|
salt: 'x',
|
|
311
|
-
fullName: 'Invitee',
|
|
312
306
|
},
|
|
313
307
|
})
|
|
314
308
|
|
|
@@ -355,7 +349,6 @@ describe('organizations service', () => {
|
|
|
355
349
|
email: 'invitee@example.com',
|
|
356
350
|
hashedPassword: 'x',
|
|
357
351
|
salt: 'x',
|
|
358
|
-
fullName: 'Invitee',
|
|
359
352
|
},
|
|
360
353
|
})
|
|
361
354
|
|
|
@@ -375,7 +368,6 @@ describe('organizations service', () => {
|
|
|
375
368
|
email: 'brand-new@example.com',
|
|
376
369
|
hashedPassword: 'x',
|
|
377
370
|
salt: 'x',
|
|
378
|
-
fullName: 'Brand New',
|
|
379
371
|
},
|
|
380
372
|
})
|
|
381
373
|
|