@cedarjs/cli 7.0.0-canary.3107 → 7.0.0-canary.3110

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.
@@ -58,13 +58,17 @@ function editSchema(schema, options) {
58
58
  if (!hasModel(schema, "User")) {
59
59
  throw new Error("CEDAR_TENANCY_ERR_NO_USER_MODEL");
60
60
  }
61
- const modelsExist = hasModel(schema, "Organization") || hasModel(schema, "Membership");
62
- const modelsAreThisCommands = schema.includes(ORGANIZATION_MODEL) && schema.includes(MEMBERSHIP_MODEL);
63
- if (modelsExist && !modelsAreThisCommands && !options.force) {
64
- throw new Error("CEDAR_TENANCY_ERR_MODELS_EXIST");
61
+ const organizationIsCustomized = hasModel(schema, "Organization") && !schema.includes(ORGANIZATION_MODEL);
62
+ const membershipIsCustomized = hasModel(schema, "Membership") && !schema.includes(MEMBERSHIP_MODEL);
63
+ const modelsCustomized = organizationIsCustomized || membershipIsCustomized;
64
+ if (modelsCustomized && !options.force) {
65
+ return { schema: addDataMigrationModel(schema), outcome: "skipped" };
65
66
  }
66
67
  const withTenancyModels = addTenancyModels(schema);
67
- return addDataMigrationModel(withTenancyModels);
68
+ return {
69
+ schema: addDataMigrationModel(withTenancyModels),
70
+ outcome: modelsCustomized ? "forced" : "added"
71
+ };
68
72
  }
69
73
  export {
70
74
  MEMBERSHIP_MODEL,
@@ -1,14 +1,13 @@
1
1
  import type { PrismaClient } from 'src/lib/db'
2
-
3
2
  import { ensureDefaultOrganization } from 'src/services/organizations/organizations'
4
3
 
5
4
  const BATCH_SIZE = 100
6
5
 
7
6
  /**
8
7
  * Existing users have no organization until this runs once: `setup tenancy`
9
- * only wires new signups (via the GraphQL `context` function and, for
10
- * dbAuth, the signup handler). Every user without a membership gets one, in
11
- * batches, using the same `ensureDefaultOrganization` new signups use.
8
+ * only wires new signups (via the GraphQL `context` function and, for dbAuth,
9
+ * the signup handler). Every user without a membership gets one, in batches,
10
+ * using the same `ensureDefaultOrganization` new signups use.
12
11
  *
13
12
  * Run with `__DATA_MIGRATE_UP_COMMAND__`, after
14
13
  * `__PRISMA_MIGRATE_DEV_COMMAND__` has applied the
@@ -16,8 +15,9 @@ const BATCH_SIZE = 100
16
15
  */
17
16
  export default async ({ db }: { db: PrismaClient }) => {
18
17
  let cursor: string | undefined
18
+ let hasMore = true
19
19
 
20
- for (;;) {
20
+ while (hasMore) {
21
21
  const users = await db.user.findMany({
22
22
  take: BATCH_SIZE,
23
23
  orderBy: { id: 'asc' },
@@ -25,10 +25,6 @@ export default async ({ db }: { db: PrismaClient }) => {
25
25
  include: { memberships: { select: { id: true } } },
26
26
  })
27
27
 
28
- if (users.length === 0) {
29
- break
30
- }
31
-
32
28
  for (const user of users) {
33
29
  if (user.memberships.length === 0) {
34
30
  await ensureDefaultOrganization({
@@ -37,10 +33,7 @@ export default async ({ db }: { db: PrismaClient }) => {
37
33
  }
38
34
  }
39
35
 
40
- cursor = users[users.length - 1].id
41
-
42
- if (users.length < BATCH_SIZE) {
43
- break
44
- }
36
+ cursor = users[users.length - 1]?.id
37
+ hasMore = users.length === BATCH_SIZE
45
38
  }
46
39
  }
@@ -22,7 +22,12 @@ import {
22
22
  hasWebAuthFile,
23
23
  noUserModelMessage
24
24
  } from "./preflight.js";
25
- import { editSchema, hasModel } from "./schemaPrisma.js";
25
+ import {
26
+ editSchema,
27
+ hasModel,
28
+ MEMBERSHIP_MODEL,
29
+ ORGANIZATION_MODEL
30
+ } from "./schemaPrisma.js";
26
31
  const DEFAULT_TENANT_FIELD = "organizationId";
27
32
  const AUTH_SNIPPET = `
28
33
  export const getCurrentUser = async (session) => {
@@ -104,12 +109,12 @@ const handler = async ({ tenantField, force }) => {
104
109
  },
105
110
  {
106
111
  title: "Adding Organization and Membership models to schema.prisma...",
107
- task: async () => {
112
+ task: async (ctx, task) => {
108
113
  const schemaPath = await getSchemaPath(paths.api.prismaConfig);
109
114
  const schema = fs.readFileSync(schemaPath, "utf-8");
110
- let updatedSchema;
115
+ let editResult;
111
116
  try {
112
- updatedSchema = editSchema(schema, { force });
117
+ editResult = editSchema(schema, { force });
113
118
  } catch (e) {
114
119
  const message = e instanceof Error ? e.message : String(e);
115
120
  if (message === "CEDAR_TENANCY_ERR_NO_USER_MODEL") {
@@ -119,14 +124,20 @@ const handler = async ({ tenantField, force }) => {
119
124
  `No \`User\` model found in schema.prisma. Set up authentication first (e.g. \`${authSetup}\`) before running \`${tenancySetup}\`.`
120
125
  );
121
126
  }
122
- if (message === "CEDAR_TENANCY_ERR_MODELS_EXIST") {
123
- throw new Error(
124
- "`Organization` and/or `Membership` models already exist in schema.prisma. Re-run with --force to keep them as-is and continue with the rest of the setup."
125
- );
126
- }
127
127
  throw e;
128
128
  }
129
- fs.writeFileSync(schemaPath, updatedSchema);
129
+ ctx.modelsExistOutcome = editResult.outcome;
130
+ fs.writeFileSync(schemaPath, editResult.schema);
131
+ if (editResult.outcome === "skipped") {
132
+ task.output = `\`Organization\` and/or \`Membership\` models already exist in schema.prisma and were left as-is. Add these fields and relations by hand so tenancy can use them:
133
+
134
+ ${ORGANIZATION_MODEL}
135
+
136
+ ${MEMBERSHIP_MODEL}
137
+
138
+ Re-run with --force to have this command append its own versions beside them instead, so you can diff and merge rather than retype.`;
139
+ return;
140
+ }
130
141
  try {
131
142
  await runTransitiveBin(
132
143
  "prisma",
@@ -134,13 +145,21 @@ const handler = async ({ tenantField, force }) => {
134
145
  { cwd: paths.base }
135
146
  );
136
147
  } catch {
148
+ if (editResult.outcome === "forced") {
149
+ task.output = '`prisma format` failed because the appended Organization/Membership clash with your existing ones, as requested by --force. Continuing with the rest of the setup; see "Next steps" for how to merge them.';
150
+ return;
151
+ }
137
152
  throw new Error(
138
153
  "`prisma format` failed, so the `memberships` relation was not added to the `User` model. Add it by hand: `memberships Membership[]` as the last field of `model User { ... }`, then re-run this command."
139
154
  );
140
155
  }
141
156
  const membershipsOccurrences = (schema2) => (schema2.match(/memberships\s+Membership\[\]/g) ?? []).length;
142
157
  const formattedSchema = fs.readFileSync(schemaPath, "utf-8");
143
- if (membershipsOccurrences(formattedSchema) <= membershipsOccurrences(updatedSchema)) {
158
+ if (membershipsOccurrences(formattedSchema) <= membershipsOccurrences(editResult.schema)) {
159
+ if (editResult.outcome === "forced") {
160
+ task.output = 'The `memberships` relation was not added to the `User` model, likely because of the clash from --force. Continuing with the rest of the setup; see "Next steps" for how to merge the appended models by hand.';
161
+ return;
162
+ }
144
163
  throw new Error(
145
164
  "`prisma format` did not add the `memberships` relation to the `User` model. Add it by hand: `memberships Membership[]` as the last field of `model User { ... }`, then re-run this command."
146
165
  );
@@ -340,17 +359,22 @@ ${SIGNUP_SNIPPET}`;
340
359
  },
341
360
  {
342
361
  title: "Next steps...",
343
- task: (_ctx, task) => {
362
+ task: (ctx, task) => {
344
363
  const migrateCommand = formatCedarCommand([
345
364
  "prisma",
346
365
  "migrate",
347
366
  "dev"
348
367
  ]);
349
368
  const dataMigrateCommand = formatCedarCommand(["data-migrate", "up"]);
369
+ const modelsExistWarning = ctx.modelsExistOutcome === "forced" ? c.error(
370
+ "\nschema.prisma is currently invalid (as requested by --force): merge the appended Organization/Membership blocks with your existing ones, then run `prisma format`.\n"
371
+ ) : ctx.modelsExistOutcome === "skipped" ? c.warning(
372
+ "\nOrganization/Membership already existed and were left as-is: add the fields and relations printed above by hand before continuing.\n"
373
+ ) : "";
350
374
  task.title = `Next steps...
351
375
 
352
376
  ${c.success("\nMulti-tenancy configured!\n")}
353
-
377
+ ${modelsExistWarning}
354
378
  1. Run ${c.highlight(migrateCommand)} to create the Organization/Membership
355
379
  tables, then ${c.highlight(dataMigrateCommand)} to give every existing
356
380
  user a default organization.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "7.0.0-canary.3107",
3
+ "version": "7.0.0-canary.3110",
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": "7.0.0-canary.3107",
40
- "@cedarjs/babel-config": "7.0.0-canary.3107",
41
- "@cedarjs/cli-helpers": "7.0.0-canary.3107",
42
- "@cedarjs/internal": "7.0.0-canary.3107",
43
- "@cedarjs/prerender": "7.0.0-canary.3107",
44
- "@cedarjs/project-config": "7.0.0-canary.3107",
45
- "@cedarjs/structure": "7.0.0-canary.3107",
46
- "@cedarjs/telemetry": "7.0.0-canary.3107",
47
- "@cedarjs/utils": "7.0.0-canary.3107",
48
- "@cedarjs/vite": "7.0.0-canary.3107",
49
- "@cedarjs/web-server": "7.0.0-canary.3107",
39
+ "@cedarjs/api-server": "7.0.0-canary.3110",
40
+ "@cedarjs/babel-config": "7.0.0-canary.3110",
41
+ "@cedarjs/cli-helpers": "7.0.0-canary.3110",
42
+ "@cedarjs/internal": "7.0.0-canary.3110",
43
+ "@cedarjs/prerender": "7.0.0-canary.3110",
44
+ "@cedarjs/project-config": "7.0.0-canary.3110",
45
+ "@cedarjs/structure": "7.0.0-canary.3110",
46
+ "@cedarjs/telemetry": "7.0.0-canary.3110",
47
+ "@cedarjs/utils": "7.0.0-canary.3110",
48
+ "@cedarjs/vite": "7.0.0-canary.3110",
49
+ "@cedarjs/web-server": "7.0.0-canary.3110",
50
50
  "@listr2/prompt-adapter-enquirer": "4.3.1",
51
51
  "@opentelemetry/api": "1.9.1",
52
52
  "@opentelemetry/core": "1.30.1",
@@ -94,7 +94,7 @@
94
94
  "yargs": "17.7.3"
95
95
  },
96
96
  "devDependencies": {
97
- "@cedarjs/framework-tools": "7.0.0-canary.3107",
97
+ "@cedarjs/framework-tools": "7.0.0-canary.3110",
98
98
  "@prisma/dmmf": "7.10.0",
99
99
  "@types/archiver": "^7.0.0",
100
100
  "memfs": "4.68.2",