@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.
@@ -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
  );
@@ -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
- acceptable: ["node20", "nodenext"]
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({ ...ctx.nameVariants, ...rest });
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', async () => {
5
- expect(${camelName}()).not.toThrow()
4
+ it('should not throw any errors', () => {
5
+ expect(() => ${camelName}()).not.toThrow()
6
6
  })
7
7
  })
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ name: '${folderName}',
6
+ // Enables global test APIs like describe, it, expect
7
+ globals: true,
8
+ },
9
+ })
@@ -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 { writeFilesWithStubsTask } from "../sdl/stubFiles.js";
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 = await files({
641
- docs,
642
- model,
643
- path: path2,
644
- tests,
645
- typescript,
646
- tailwind,
647
- force
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
- printRedactedFieldsNote(await redactedSensitiveFields([name]));
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 Args {
7
- _: string[]
8
- [key: string]: unknown
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 }: 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)
@@ -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 { serveStatic } = await import("srvx/static");
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
- serveStatic({ dir: webDist }),
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: free
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) => /^import { FatalErrorBoundary, RedwoodProvider } from '@cedarjs\/web'$/.test(
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
- "import { RedwoodProvider } from '@cedarjs/web'"
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
- async function handler({ force }) {
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
- hasExistingDatabaseUrl = /^DATABASE_URL=/m.test(
27
- fs.readFileSync(envPath, "utf-8")
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
- "Prisma migration failed:\n\n" + result.stderr + `
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
- if (force) {
156
- const lines = envContent.split("\n");
157
- const filtered = lines.filter(
158
- (line) => !line.startsWith("DATABASE_URL=") && !line.startsWith("DIRECT_DATABASE_URL=")
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
- within: "RedwoodProvider",
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
- within: "RedwoodProvider"
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(
@@ -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.241",
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.241",
40
- "@cedarjs/babel-config": "6.0.0-rc.241",
41
- "@cedarjs/cli-helpers": "6.0.0-rc.241",
42
- "@cedarjs/internal": "6.0.0-rc.241",
43
- "@cedarjs/prerender": "6.0.0-rc.241",
44
- "@cedarjs/project-config": "6.0.0-rc.241",
45
- "@cedarjs/structure": "6.0.0-rc.241",
46
- "@cedarjs/telemetry": "6.0.0-rc.241",
47
- "@cedarjs/utils": "6.0.0-rc.241",
48
- "@cedarjs/vite": "6.0.0-rc.241",
49
- "@cedarjs/web-server": "6.0.0-rc.241",
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.7.4",
86
- "smol-toml": "1.6.1",
87
- "srvx": "0.11.16",
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.241",
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.64.0",
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",