@cedarjs/cli 6.0.0-rc.241 → 6.0.0-rc.312
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/dev/devHandler.js +38 -1
- package/dist/commands/dev.js +3 -0
- package/dist/commands/execHandler.js +12 -0
- package/dist/commands/generate/package/filesTask.js +12 -0
- package/dist/commands/generate/package/packageHandler.js +15 -5
- package/dist/commands/generate/package/templates/test.ts.template +2 -2
- package/dist/commands/generate/package/templates/vitest.config.ts.template +9 -0
- package/dist/commands/generate/scaffold/scaffoldHandler.js +54 -13
- package/dist/commands/generate/script/templates/script.ts.template +9 -4
- package/dist/commands/serve.js +2 -2
- package/dist/commands/setup/deploy/providers/renderHandler.js +23 -1
- package/dist/commands/setup/deploy/templates/render.js +5 -2
- package/dist/commands/setup/monitoring/sentry/sentryHandler.js +9 -4
- package/dist/commands/setup/neon/neon.js +13 -3
- package/dist/commands/setup/neon/neonHandler.js +54 -13
- package/dist/commands/setup/ui/libraries/chakra-uiHandler.js +3 -1
- package/dist/commands/setup/ui/libraries/mantineHandler.js +3 -1
- package/dist/commands/test/testHandlerEsm.js +1 -1
- package/dist/lib/extendFile.js +1 -1
- package/package.json +17 -17
|
@@ -48,7 +48,8 @@ const handler = async ({
|
|
|
48
48
|
apiDebugPort,
|
|
49
49
|
debugBrk,
|
|
50
50
|
ud = false,
|
|
51
|
-
nodeArgs = ""
|
|
51
|
+
nodeArgs = "",
|
|
52
|
+
jobs: jobsOption
|
|
52
53
|
}) => {
|
|
53
54
|
recordTelemetryAttributes({
|
|
54
55
|
command: "dev",
|
|
@@ -206,6 +207,11 @@ const handler = async ({
|
|
|
206
207
|
` --apiPort ${apiAvailablePort}`,
|
|
207
208
|
getApiDebugFlag(apiDebugPort, apiAvailablePort),
|
|
208
209
|
debugBrk ? "--debug-brk" : "",
|
|
210
|
+
// `cedar-unified-dev` starts its own in-process jobs worker pool (see
|
|
211
|
+
// `jobsDevMiddleware.ts`) rather than relying on the nodemon-wrapped
|
|
212
|
+
// worker pushed below, so `--no-jobs` has to be forwarded explicitly -
|
|
213
|
+
// it isn't part of `forward` unless the user passed it after `--`.
|
|
214
|
+
jobsOption === false ? "--no-jobs" : "",
|
|
209
215
|
forward
|
|
210
216
|
].join(" ").replace(/\s+/g, " ").trim();
|
|
211
217
|
};
|
|
@@ -280,6 +286,37 @@ const handler = async ({
|
|
|
280
286
|
prefixColor: "green"
|
|
281
287
|
});
|
|
282
288
|
}
|
|
289
|
+
const jobsConfigured = jobsOption !== false && workspace.includes("api") && !!cedarPaths.api.jobsConfig && // `getPaths()` resolves `jobsConfig` once and caches it in-process, so
|
|
290
|
+
// if `api/src/lib/jobs.ts` is deleted mid-session the cached path would
|
|
291
|
+
// otherwise still look "configured". Guard against starting a worker
|
|
292
|
+
// that can't load a jobs config that no longer exists.
|
|
293
|
+
fs.existsSync(cedarPaths.api.jobsConfig) && fs.existsSync(cedarPaths.api.jobs) && // Job files always live in `api/src/jobs/<ComponentName>Job/`
|
|
294
|
+
// subdirectories (see `generate/job/jobHandler.ts`), so entries here are
|
|
295
|
+
// directories, not files — only the `.keep` placeholder (and any stray
|
|
296
|
+
// dotfiles, e.g. `.DS_Store`) should be excluded.
|
|
297
|
+
fs.readdirSync(cedarPaths.api.jobs).some((entry) => !entry.startsWith("."));
|
|
298
|
+
if (jobsConfigured && unifiedDevCommand) {
|
|
299
|
+
} else if (jobsConfigured) {
|
|
300
|
+
jobs.push({
|
|
301
|
+
name: "jobs",
|
|
302
|
+
// `cedar-jobs work` loads its config and job files from `api/dist`
|
|
303
|
+
// (compiled output), not `api/src`. That dist output is only written
|
|
304
|
+
// once the `api` watcher's initial build finishes, which happens
|
|
305
|
+
// asynchronously — so on a clean `cedar dev` start there's a window
|
|
306
|
+
// where `api/dist` doesn't exist yet and the worker would exit
|
|
307
|
+
// immediately. Wrapping it in nodemon (same tool the `api` job above
|
|
308
|
+
// uses) means it retries as soon as `api/dist` changes, instead of
|
|
309
|
+
// staying dead for the rest of the session. This also means the
|
|
310
|
+
// worker restarts automatically whenever job code is rebuilt, since
|
|
311
|
+
// Node's ESM cache would otherwise keep serving stale job code.
|
|
312
|
+
command: formatRunBinCommand("nodemon", [
|
|
313
|
+
"--quiet",
|
|
314
|
+
`--watch "${cedarPaths.api.dist}"`,
|
|
315
|
+
`--exec "${formatRunBinCommand("cedar-jobs", ["work"])}"`
|
|
316
|
+
]),
|
|
317
|
+
prefixColor: "magenta"
|
|
318
|
+
});
|
|
319
|
+
}
|
|
283
320
|
const packageWorkspaces = workspace.filter(
|
|
284
321
|
(w) => w !== "api" && w !== "web" && w !== "gen"
|
|
285
322
|
);
|
package/dist/commands/dev.js
CHANGED
|
@@ -31,6 +31,9 @@ const builder = (yargs) => {
|
|
|
31
31
|
}).option("nodeArgs", {
|
|
32
32
|
type: "string",
|
|
33
33
|
description: 'CLI args to pass to the node process running the web dev server, for example: `--node-args="--inspect --max-old-space-size=8192"`.'
|
|
34
|
+
}).option("jobs", {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
description: "Start the background jobs worker alongside the other dev servers when jobs are configured (`cedar setup jobs` + at least one `cedar g job`). Pass `--no-jobs` to opt out and run `cedar jobs work` yourself instead."
|
|
34
37
|
}).middleware(() => {
|
|
35
38
|
const check = checkNodeVersion();
|
|
36
39
|
if (check.ok) {
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { context } from "@opentelemetry/api";
|
|
4
4
|
import { suppressTracing } from "@opentelemetry/core";
|
|
5
5
|
import { Listr } from "listr2";
|
|
6
|
+
import { Parser } from "yargs/helpers";
|
|
6
7
|
import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
|
|
7
8
|
import { findScripts } from "@cedarjs/internal/dist/files";
|
|
8
9
|
import { runScriptFunction } from "../lib/exec.js";
|
|
@@ -50,6 +51,17 @@ const handler = async (args) => {
|
|
|
50
51
|
delete scriptArgs.l;
|
|
51
52
|
delete scriptArgs.s;
|
|
52
53
|
delete scriptArgs.silent;
|
|
54
|
+
if (Array.isArray(scriptArgs._)) {
|
|
55
|
+
const dashBlockIndex = scriptArgs._.findIndex(
|
|
56
|
+
(arg) => typeof arg === "string" && arg.startsWith("-")
|
|
57
|
+
);
|
|
58
|
+
if (dashBlockIndex !== -1) {
|
|
59
|
+
const unparsedTail = scriptArgs._.splice(dashBlockIndex);
|
|
60
|
+
const { _: reparsedPositionals, ...reparsedFlags } = Parser(unparsedTail);
|
|
61
|
+
scriptArgs._.push(...reparsedPositionals);
|
|
62
|
+
Object.assign(scriptArgs, reparsedFlags);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
53
65
|
const scriptPath = resolveScriptPath(name);
|
|
54
66
|
if (!scriptPath) {
|
|
55
67
|
console.error(
|
|
@@ -8,6 +8,7 @@ const files = async ({
|
|
|
8
8
|
fileName,
|
|
9
9
|
typescript,
|
|
10
10
|
tests: generateTests = true,
|
|
11
|
+
esm = false,
|
|
11
12
|
...rest
|
|
12
13
|
}) => {
|
|
13
14
|
const extension = typescript ? ".ts" : ".js";
|
|
@@ -58,6 +59,17 @@ const files = async ({
|
|
|
58
59
|
outputPath: path.join(folderName, "src", `${fileName}.test${extension}`)
|
|
59
60
|
});
|
|
60
61
|
outputFiles.push(testFile);
|
|
62
|
+
if (esm) {
|
|
63
|
+
const vitestConfigFile = await templateForFile({
|
|
64
|
+
name,
|
|
65
|
+
side: "packages",
|
|
66
|
+
generator: "package",
|
|
67
|
+
templatePath: "vitest.config.ts.template",
|
|
68
|
+
templateVars: { folderName, ...rest },
|
|
69
|
+
outputPath: path.join(folderName, `vitest.config${extension}`)
|
|
70
|
+
});
|
|
71
|
+
outputFiles.push(vitestConfigFile);
|
|
72
|
+
}
|
|
61
73
|
}
|
|
62
74
|
return outputFiles.reduce(async (accP, [outputPath, content]) => {
|
|
63
75
|
const acc = await accP;
|
|
@@ -11,11 +11,11 @@ import { workspacePackageSpecifier } from "@cedarjs/cli-helpers/packageManager";
|
|
|
11
11
|
import { runScript, runBinSync } from "@cedarjs/cli-helpers/packageManager/exec";
|
|
12
12
|
import { installPackages } from "@cedarjs/cli-helpers/packageManager/packages";
|
|
13
13
|
import { addWorkspaceDir } from "@cedarjs/cli-helpers/packageManager/workspaces";
|
|
14
|
-
import { getConfig } from "@cedarjs/project-config";
|
|
14
|
+
import { getConfig, projectIsEsm } from "@cedarjs/project-config";
|
|
15
15
|
import { getPackageManager } from "@cedarjs/project-config/packageManager";
|
|
16
16
|
import { errorTelemetry } from "@cedarjs/telemetry";
|
|
17
17
|
import { getPaths, writeFilesTask } from "../../../lib/index.js";
|
|
18
|
-
import { prepareForRollback } from "../../../lib/rollback.js";
|
|
18
|
+
import { addFileToRollback, prepareForRollback } from "../../../lib/rollback.js";
|
|
19
19
|
import { files } from "./filesTask.js";
|
|
20
20
|
function nameVariants(nameArg) {
|
|
21
21
|
const base = path.basename(getPaths().base);
|
|
@@ -32,8 +32,10 @@ async function updateTsconfig(task) {
|
|
|
32
32
|
path: path.join(getPaths().api.base, "tsconfig.json"),
|
|
33
33
|
expectedModule: "node20",
|
|
34
34
|
// While Cedar doesn't officially endorse NodeNext, it will still work
|
|
35
|
-
// here, so we'll keep it
|
|
36
|
-
|
|
35
|
+
// here, so we'll keep it. ESNext is also acceptable: it's the current
|
|
36
|
+
// create-cedar-app default, paired with `moduleResolution: bundler` so
|
|
37
|
+
// extensionless scaffold/generator imports resolve correctly.
|
|
38
|
+
acceptable: ["node20", "nodenext", "esnext"]
|
|
37
39
|
},
|
|
38
40
|
{
|
|
39
41
|
name: "web",
|
|
@@ -246,6 +248,9 @@ function updateWorkspaceTsconfigReferences(task, folderName, targetWorkspaces) {
|
|
|
246
248
|
async function installAndBuild(folderName) {
|
|
247
249
|
const packagePath = path.join("packages", folderName);
|
|
248
250
|
await installPackages({ stdio: "inherit", cwd: getPaths().base });
|
|
251
|
+
addFileToRollback(
|
|
252
|
+
path.join(getPaths().base, packagePath, "tsconfig.tsbuildinfo")
|
|
253
|
+
);
|
|
249
254
|
await runScript("build", [], { stdio: "inherit", cwd: packagePath });
|
|
250
255
|
}
|
|
251
256
|
const handler = async ({
|
|
@@ -335,7 +340,12 @@ const handler = async ({
|
|
|
335
340
|
{
|
|
336
341
|
title: "Generating package files...",
|
|
337
342
|
task: async (ctx) => {
|
|
338
|
-
packageFiles = await files({
|
|
343
|
+
packageFiles = await files({
|
|
344
|
+
...ctx.nameVariants,
|
|
345
|
+
// Only Vitest projects get a config generated for them
|
|
346
|
+
esm: projectIsEsm(),
|
|
347
|
+
...rest
|
|
348
|
+
});
|
|
339
349
|
return writeFilesTask(packageFiles, { overwriteExisting: force });
|
|
340
350
|
}
|
|
341
351
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ${camelName} } from './index.js'
|
|
2
2
|
|
|
3
3
|
describe('${camelName}', () => {
|
|
4
|
-
it('should not throw any errors',
|
|
5
|
-
expect(${camelName}()).not.toThrow()
|
|
4
|
+
it('should not throw any errors', () => {
|
|
5
|
+
expect(() => ${camelName}()).not.toThrow()
|
|
6
6
|
})
|
|
7
7
|
})
|
|
@@ -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;
|
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
// To access your database uncomment the line below
|
|
4
4
|
// import { db } from 'api/src/lib/db'
|
|
5
5
|
|
|
6
|
-
interface
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
interface ScriptArgs {
|
|
7
|
+
args: {
|
|
8
|
+
// positional args, e.g. `yarn cedar exec myScript foo 123` -> ['foo', 123]
|
|
9
|
+
// numeric-looking args are parsed as numbers, others as strings
|
|
10
|
+
_: Array<string | number>
|
|
11
|
+
// named flags, e.g. `--force` -> { force: true }
|
|
12
|
+
[flag: string]: unknown
|
|
13
|
+
}
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
export default async ({ args }:
|
|
16
|
+
export default async ({ args }: ScriptArgs) => {
|
|
12
17
|
// Your script here...
|
|
13
18
|
console.log(':: Executing script with args ::')
|
|
14
19
|
console.log(args)
|
package/dist/commands/serve.js
CHANGED
|
@@ -107,7 +107,7 @@ const builder = async (yargs) => {
|
|
|
107
107
|
const webHost = argv.webHost ?? getWebHost({ isPublicSide: true });
|
|
108
108
|
const apiRootPath = argv.apiRootPath ?? "/";
|
|
109
109
|
const apiTarget = `http://${apiHost.includes(":") ? `[${apiHost}]` : apiHost}:${apiPort}`;
|
|
110
|
-
const {
|
|
110
|
+
const { staticMiddleware } = await import("srvx/static");
|
|
111
111
|
const apiUrl = getConfig().web.apiUrl;
|
|
112
112
|
const webDist = getPaths().web.dist;
|
|
113
113
|
const prerenderIndexPath = path.join(webDist, "200.html");
|
|
@@ -117,7 +117,7 @@ const builder = async (yargs) => {
|
|
|
117
117
|
// Dummy fetch handler. All requests are handled by middleware
|
|
118
118
|
fetch: async () => new Response("Not Found", { status: 404 }),
|
|
119
119
|
middleware: [
|
|
120
|
-
|
|
120
|
+
staticMiddleware({ dir: webDist }),
|
|
121
121
|
async (req, next) => {
|
|
122
122
|
const url = new URL(req.url, "http://localhost");
|
|
123
123
|
if (!url.pathname.startsWith(apiUrl)) {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import prismaInternals from "@prisma/internals";
|
|
3
3
|
import { Listr } from "listr2";
|
|
4
|
+
import prompts from "prompts";
|
|
4
5
|
import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
|
|
5
6
|
import { getPaths, getPrismaSchemas } from "@cedarjs/project-config";
|
|
6
7
|
import { errorTelemetry } from "@cedarjs/telemetry";
|
|
7
8
|
import { writeFilesTask, printSetupNotes } from "../../../../lib/index.js";
|
|
8
9
|
import { POSTGRES_YAML, RENDER_YAML, SQLITE_YAML } from "../templates/render.js";
|
|
9
10
|
const { getConfig } = prismaInternals;
|
|
11
|
+
const SQLITE_API_PLAN = "starter";
|
|
10
12
|
const getRenderYamlContent = async (database) => {
|
|
11
13
|
if (database === "none") {
|
|
12
14
|
return {
|
|
@@ -27,7 +29,7 @@ const getRenderYamlContent = async (database) => {
|
|
|
27
29
|
case "sqlite":
|
|
28
30
|
return {
|
|
29
31
|
path: path.join(getPaths().base, "render.yaml"),
|
|
30
|
-
content: RENDER_YAML(SQLITE_YAML)
|
|
32
|
+
content: RENDER_YAML(SQLITE_YAML, SQLITE_API_PLAN)
|
|
31
33
|
};
|
|
32
34
|
default:
|
|
33
35
|
throw new Error(`
|
|
@@ -61,6 +63,26 @@ const handler = async ({
|
|
|
61
63
|
force,
|
|
62
64
|
database
|
|
63
65
|
});
|
|
66
|
+
if (database === "sqlite") {
|
|
67
|
+
console.warn(
|
|
68
|
+
c.warning(
|
|
69
|
+
`Render's free plan doesn't support persistent disks, which the \`sqlite\` deploy option requires for its database file. The generated render.yaml will set the api service's plan to "${SQLITE_API_PLAN}" (a paid plan) instead of "free" so the disk can actually attach.
|
|
70
|
+
|
|
71
|
+
If you want to stay on the free plan, rerun this command with \`--database postgresql\` (a managed database, not a disk) or \`--database none\`.`
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
console.log();
|
|
75
|
+
const { confirmed } = await prompts({
|
|
76
|
+
type: "confirm",
|
|
77
|
+
name: "confirmed",
|
|
78
|
+
message: `Generate render.yaml with the api service on the "${SQLITE_API_PLAN}" plan?`
|
|
79
|
+
});
|
|
80
|
+
if (!confirmed) {
|
|
81
|
+
console.log("Aborting render setup.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log();
|
|
85
|
+
}
|
|
64
86
|
const tasks = new Listr(
|
|
65
87
|
[
|
|
66
88
|
{
|
|
@@ -2,7 +2,7 @@ import path from "path";
|
|
|
2
2
|
import { getPaths } from "../../../../lib/index.js";
|
|
3
3
|
import { getUserApiUrl } from "../helpers/index.js";
|
|
4
4
|
const PROJECT_NAME = path.basename(getPaths().base);
|
|
5
|
-
const RENDER_YAML = (database) => {
|
|
5
|
+
const RENDER_YAML = (database, plan = "free") => {
|
|
6
6
|
const apiUrl = getUserApiUrl().replace(/\/$/, "");
|
|
7
7
|
return `# Quick links to the docs:
|
|
8
8
|
# - Deploying Cedar: https://cedarjs.com/docs/deploy/render
|
|
@@ -41,7 +41,7 @@ services:
|
|
|
41
41
|
|
|
42
42
|
- name: ${PROJECT_NAME}-api
|
|
43
43
|
type: web
|
|
44
|
-
plan:
|
|
44
|
+
plan: ${plan}
|
|
45
45
|
runtime: node
|
|
46
46
|
region: oregon
|
|
47
47
|
buildCommand: npm install --global corepack && yarn install && yarn cedar build api
|
|
@@ -69,9 +69,12 @@ const POSTGRES_YAML = ` - key: DATABASE_URL
|
|
|
69
69
|
|
|
70
70
|
databases:
|
|
71
71
|
- name: ${PROJECT_NAME}-db
|
|
72
|
+
plan: free
|
|
72
73
|
region: oregon`;
|
|
73
74
|
const SQLITE_YAML = ` - key: DATABASE_URL
|
|
74
75
|
value: file:./data/sqlite.db
|
|
76
|
+
# Persistent disks aren't available on Render's free plan, which is why
|
|
77
|
+
# the api service above is on a paid plan when SQLite is selected.
|
|
75
78
|
disk:
|
|
76
79
|
name: sqlite-data
|
|
77
80
|
mountPath: /opt/render/project/src/api/db/data
|
|
@@ -90,15 +90,20 @@ const handler = async ({ force }) => {
|
|
|
90
90
|
title: "Replacing Redwood's Error boundary",
|
|
91
91
|
task: async () => {
|
|
92
92
|
const contentLines = fs.readFileSync(rwPaths.web.app).toString().split("\n");
|
|
93
|
+
const webImportRe = /^import \{ FatalErrorBoundary, ((?:Cedar|Redwood)Provider) \} from '@cedarjs\/web'$/;
|
|
93
94
|
const webImportIndex = contentLines.findLastIndex(
|
|
94
|
-
(line) =>
|
|
95
|
-
line
|
|
96
|
-
)
|
|
95
|
+
(line) => webImportRe.test(line)
|
|
97
96
|
);
|
|
97
|
+
if (webImportIndex === -1) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Could not find "import { FatalErrorBoundary, CedarProvider } from '@cedarjs/web'" in web/src/App`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const providerName = contentLines[webImportIndex].match(webImportRe)?.[1];
|
|
98
103
|
contentLines.splice(
|
|
99
104
|
webImportIndex,
|
|
100
105
|
1,
|
|
101
|
-
|
|
106
|
+
`import { ${providerName} } from '@cedarjs/web'`
|
|
102
107
|
);
|
|
103
108
|
const boundaryOpenIndex = contentLines.findLastIndex(
|
|
104
109
|
(line) => line.includes("<FatalErrorBoundary page={FatalErrorPage}>")
|
|
@@ -7,15 +7,25 @@ function builder(yargs) {
|
|
|
7
7
|
default: false,
|
|
8
8
|
description: "Overwrite existing DATABASE_URL in .env",
|
|
9
9
|
type: "boolean"
|
|
10
|
+
}).option("migrations", {
|
|
11
|
+
description: "Run Prisma migrations after setup. Omit to be prompted. Use --no-migrations to skip.",
|
|
12
|
+
type: "boolean"
|
|
13
|
+
}).option("verbose", {
|
|
14
|
+
alias: "v",
|
|
15
|
+
default: false,
|
|
16
|
+
description: "Show full output from migration commands (stderr visible in terminal)",
|
|
17
|
+
type: "boolean"
|
|
10
18
|
});
|
|
11
19
|
}
|
|
12
|
-
async function handler({ force }) {
|
|
20
|
+
async function handler({ force, migrations, verbose }) {
|
|
13
21
|
recordTelemetryAttributes({
|
|
14
22
|
command: "setup neon",
|
|
15
|
-
force
|
|
23
|
+
force,
|
|
24
|
+
migrations,
|
|
25
|
+
verbose
|
|
16
26
|
});
|
|
17
27
|
const { handler: handler2 } = await import("./neonHandler.js");
|
|
18
|
-
return handler2({ force });
|
|
28
|
+
return handler2({ force, migrations, verbose });
|
|
19
29
|
}
|
|
20
30
|
export {
|
|
21
31
|
builder,
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import execa from "execa";
|
|
4
4
|
import { Listr } from "listr2";
|
|
5
|
+
import prompts from "prompts";
|
|
5
6
|
import { colors, getPaths, installPackages } from "@cedarjs/cli-helpers";
|
|
6
7
|
import { prettyPrintCedarCommand } from "@cedarjs/cli-helpers/packageManager";
|
|
7
8
|
import { errorTelemetry } from "@cedarjs/telemetry";
|
|
@@ -9,7 +10,23 @@ import {
|
|
|
9
10
|
checkProjectShape,
|
|
10
11
|
getSqliteToPostgresTasks
|
|
11
12
|
} from "../database/postgresHandler.js";
|
|
12
|
-
|
|
13
|
+
function isPostgresConnectionString(rawValue) {
|
|
14
|
+
let value = rawValue.trim();
|
|
15
|
+
const quoted = value.match(/^(['"])(.*)\1$/);
|
|
16
|
+
if (quoted) {
|
|
17
|
+
value = quoted[2];
|
|
18
|
+
}
|
|
19
|
+
if (!value) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(value);
|
|
24
|
+
return (url.protocol === "postgres:" || url.protocol === "postgresql:") && !!url.hostname;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function handler({ force, migrations, verbose }) {
|
|
13
30
|
const cedarPaths = getPaths();
|
|
14
31
|
const shape = checkProjectShape(cedarPaths);
|
|
15
32
|
if (!shape.ok) {
|
|
@@ -23,9 +40,8 @@ async function handler({ force }) {
|
|
|
23
40
|
const envPath = path.join(cedarPaths.base, ".env");
|
|
24
41
|
let hasExistingDatabaseUrl = false;
|
|
25
42
|
if (fs.existsSync(envPath)) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
);
|
|
43
|
+
const match = fs.readFileSync(envPath, "utf-8").match(/^DATABASE_URL=(.*)$/m);
|
|
44
|
+
hasExistingDatabaseUrl = !!match?.[1] && isPostgresConnectionString(match[1]);
|
|
29
45
|
}
|
|
30
46
|
const skipProvisioning = hasExistingDatabaseUrl && !force;
|
|
31
47
|
const notes = [];
|
|
@@ -36,6 +52,29 @@ async function handler({ force }) {
|
|
|
36
52
|
)
|
|
37
53
|
);
|
|
38
54
|
}
|
|
55
|
+
let runMigrations = migrations;
|
|
56
|
+
if (!skipProvisioning && runMigrations === void 0) {
|
|
57
|
+
if (!process.stdin.isTTY) {
|
|
58
|
+
const error = new Error(
|
|
59
|
+
"Cannot prompt for confirmation in a non-interactive terminal. Please pass --migrations or --no-migrations explicitly."
|
|
60
|
+
);
|
|
61
|
+
errorTelemetry(process.argv, error.message);
|
|
62
|
+
console.error(colors.error(error.message));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const response = await prompts({
|
|
66
|
+
type: "toggle",
|
|
67
|
+
name: "runMigrations",
|
|
68
|
+
message: "Run Prisma migrations now?",
|
|
69
|
+
initial: true,
|
|
70
|
+
active: "Yes",
|
|
71
|
+
inactive: "No"
|
|
72
|
+
});
|
|
73
|
+
if (response.runMigrations === void 0) {
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
runMigrations = response.runMigrations;
|
|
77
|
+
}
|
|
39
78
|
const tasks = new Listr(
|
|
40
79
|
[
|
|
41
80
|
...getSqliteToPostgresTasks({ dbPath: shape.dbPath }),
|
|
@@ -109,6 +148,9 @@ async function handler({ force }) {
|
|
|
109
148
|
if (skipProvisioning) {
|
|
110
149
|
return true;
|
|
111
150
|
}
|
|
151
|
+
if (!runMigrations) {
|
|
152
|
+
return migrations === false ? "Skipped (--no-migrations)" : "Skipped";
|
|
153
|
+
}
|
|
112
154
|
if (ctx.directDatabaseUrlNotSet) {
|
|
113
155
|
return `Skipping migrations \u2014 could not confirm prisma.config is reading DIRECT_DATABASE_URL, so migrations could target the wrong database. Fix datasource.url, then run \`${prettyPrintCedarCommand(["prisma", "migrate", "dev"])}\` manually.`;
|
|
114
156
|
}
|
|
@@ -119,7 +161,7 @@ async function handler({ force }) {
|
|
|
119
161
|
"yarn cedar prisma migrate dev --name init-neon",
|
|
120
162
|
{
|
|
121
163
|
cwd: cedarPaths.base,
|
|
122
|
-
stdio: ["inherit", "inherit", "pipe"],
|
|
164
|
+
stdio: verbose ? "inherit" : ["inherit", "inherit", "pipe"],
|
|
123
165
|
reject: false,
|
|
124
166
|
env: {
|
|
125
167
|
...process.env,
|
|
@@ -129,7 +171,8 @@ async function handler({ force }) {
|
|
|
129
171
|
);
|
|
130
172
|
if (result.exitCode !== 0) {
|
|
131
173
|
throw new Error(
|
|
132
|
-
|
|
174
|
+
verbose ? `Prisma migration failed. You can try running it manually:
|
|
175
|
+
${prettyPrintCedarCommand(["prisma", "migrate", "dev", "--name", "init-neon"])}` : "Prisma migration failed:\n\n" + result.stderr + `
|
|
133
176
|
|
|
134
177
|
You can try running it manually:
|
|
135
178
|
${prettyPrintCedarCommand(["prisma", "migrate", "dev", "--name", "init-neon"])}`
|
|
@@ -152,13 +195,11 @@ You can try running it manually:
|
|
|
152
195
|
let envContent = "";
|
|
153
196
|
if (fs.existsSync(envPath)) {
|
|
154
197
|
envContent = fs.readFileSync(envPath, "utf-8");
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
envContent = filtered.join("\n").trimEnd();
|
|
161
|
-
}
|
|
198
|
+
const lines = envContent.split("\n");
|
|
199
|
+
const filtered = lines.filter(
|
|
200
|
+
(line) => !line.startsWith("DATABASE_URL=") && !line.startsWith("DIRECT_DATABASE_URL=")
|
|
201
|
+
);
|
|
202
|
+
envContent = filtered.join("\n").trimEnd();
|
|
162
203
|
if (envContent && !envContent.endsWith("\n")) {
|
|
163
204
|
envContent += "\n";
|
|
164
205
|
}
|
|
@@ -54,7 +54,9 @@ async function handler({
|
|
|
54
54
|
insertComponent: {
|
|
55
55
|
name: "ChakraProvider",
|
|
56
56
|
props: { theme: "extendedTheme" },
|
|
57
|
-
|
|
57
|
+
// Older projects (created before CedarProvider replaced the
|
|
58
|
+
// deprecated RedwoodProvider) may still use RedwoodProvider.
|
|
59
|
+
within: "CedarProvider|RedwoodProvider",
|
|
58
60
|
insertBefore: "<ColorModeScript />"
|
|
59
61
|
},
|
|
60
62
|
imports: [
|
|
@@ -77,7 +77,9 @@ async function handler({
|
|
|
77
77
|
insertComponent: {
|
|
78
78
|
name: "MantineProvider",
|
|
79
79
|
props: { theme: "theme" },
|
|
80
|
-
|
|
80
|
+
// Older projects (created before CedarProvider replaced the
|
|
81
|
+
// deprecated RedwoodProvider) may still use RedwoodProvider.
|
|
82
|
+
within: "CedarProvider|RedwoodProvider"
|
|
81
83
|
},
|
|
82
84
|
imports: [
|
|
83
85
|
"import { MantineProvider } from '@mantine/core'",
|
|
@@ -59,10 +59,10 @@ const handler = async ({
|
|
|
59
59
|
vitestArgs.push("--run");
|
|
60
60
|
}
|
|
61
61
|
if (!others["config"]) {
|
|
62
|
+
sides.forEach((side) => vitestArgs.push("--project", side));
|
|
62
63
|
if (!sides.length) {
|
|
63
64
|
project.workspaces().forEach((side) => sides.push(side));
|
|
64
65
|
}
|
|
65
|
-
sides.forEach((side) => vitestArgs.push("--project", side));
|
|
66
66
|
}
|
|
67
67
|
try {
|
|
68
68
|
const cacheDirDb = `file:${ensurePosixPath(
|
package/dist/lib/extendFile.js
CHANGED
|
@@ -56,7 +56,7 @@ function insertComponent(content, {
|
|
|
56
56
|
"Exactly one of (around | within) must be defined. Choose one."
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
|
-
const target = around ?? within
|
|
59
|
+
const target = `(?:${around ?? within})`;
|
|
60
60
|
const findTagIndex = (regex) => content.findIndex((line) => regex.test(line));
|
|
61
61
|
let open = findTagIndex(new RegExp(`([^\\S\r
|
|
62
62
|
]*)<${target}\\s*(.*)\\s*>`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/cli",
|
|
3
|
-
"version": "6.0.0-rc.
|
|
3
|
+
"version": "6.0.0-rc.312",
|
|
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-rc.
|
|
40
|
-
"@cedarjs/babel-config": "6.0.0-rc.
|
|
41
|
-
"@cedarjs/cli-helpers": "6.0.0-rc.
|
|
42
|
-
"@cedarjs/internal": "6.0.0-rc.
|
|
43
|
-
"@cedarjs/prerender": "6.0.0-rc.
|
|
44
|
-
"@cedarjs/project-config": "6.0.0-rc.
|
|
45
|
-
"@cedarjs/structure": "6.0.0-rc.
|
|
46
|
-
"@cedarjs/telemetry": "6.0.0-rc.
|
|
47
|
-
"@cedarjs/utils": "6.0.0-rc.
|
|
48
|
-
"@cedarjs/vite": "6.0.0-rc.
|
|
49
|
-
"@cedarjs/web-server": "6.0.0-rc.
|
|
39
|
+
"@cedarjs/api-server": "6.0.0-rc.312",
|
|
40
|
+
"@cedarjs/babel-config": "6.0.0-rc.312",
|
|
41
|
+
"@cedarjs/cli-helpers": "6.0.0-rc.312",
|
|
42
|
+
"@cedarjs/internal": "6.0.0-rc.312",
|
|
43
|
+
"@cedarjs/prerender": "6.0.0-rc.312",
|
|
44
|
+
"@cedarjs/project-config": "6.0.0-rc.312",
|
|
45
|
+
"@cedarjs/structure": "6.0.0-rc.312",
|
|
46
|
+
"@cedarjs/telemetry": "6.0.0-rc.312",
|
|
47
|
+
"@cedarjs/utils": "6.0.0-rc.312",
|
|
48
|
+
"@cedarjs/vite": "6.0.0-rc.312",
|
|
49
|
+
"@cedarjs/web-server": "6.0.0-rc.312",
|
|
50
50
|
"@listr2/prompt-adapter-enquirer": "4.3.0",
|
|
51
51
|
"@opentelemetry/api": "1.9.1",
|
|
52
52
|
"@opentelemetry/core": "1.30.1",
|
|
@@ -82,9 +82,9 @@
|
|
|
82
82
|
"prettier": "3.8.4",
|
|
83
83
|
"prisma": "7.8.0",
|
|
84
84
|
"prompts": "2.4.2",
|
|
85
|
-
"semver": "7.
|
|
86
|
-
"smol-toml": "1.
|
|
87
|
-
"srvx": "0.
|
|
85
|
+
"semver": "7.8.5",
|
|
86
|
+
"smol-toml": "1.8.0",
|
|
87
|
+
"srvx": "0.12.5",
|
|
88
88
|
"string-env-interpolation": "1.0.1",
|
|
89
89
|
"systeminformation": "5.31.7",
|
|
90
90
|
"termi-link": "1.1.0",
|
|
@@ -94,10 +94,10 @@
|
|
|
94
94
|
"yargs": "17.7.3"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@cedarjs/framework-tools": "6.0.0-rc.
|
|
97
|
+
"@cedarjs/framework-tools": "6.0.0-rc.312",
|
|
98
98
|
"@prisma/dmmf": "7.8.0",
|
|
99
99
|
"@types/archiver": "^7.0.0",
|
|
100
|
-
"memfs": "4.
|
|
100
|
+
"memfs": "4.68.1",
|
|
101
101
|
"node-ssh": "13.2.1",
|
|
102
102
|
"ts-dedent": "2.3.0",
|
|
103
103
|
"typescript": "5.9.3",
|