@cedarjs/cli 7.0.0-canary.3106 → 7.0.0-canary.3108
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/setup/tenancy/preflight.js +17 -14
- package/dist/commands/setup/tenancy/schemaPrisma.js +9 -5
- package/dist/commands/setup/tenancy/templates/ensureDefaultOrganizations.dataMigration.ts.template +3 -2
- package/dist/commands/setup/tenancy/tenancyHandler.js +64 -29
- package/package.json +13 -13
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
|
|
3
4
|
const WEB_AUTH_EXTENSIONS = ["ts", "tsx", "js", "jsx"];
|
|
4
5
|
function hasWebAuthFile(webSrcPath) {
|
|
5
6
|
return WEB_AUTH_EXTENSIONS.some(
|
|
6
7
|
(extension) => fs.existsSync(path.join(webSrcPath, `auth.${extension}`))
|
|
7
8
|
);
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"",
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
function authNotSetUpMessage() {
|
|
11
|
+
return [
|
|
12
|
+
"Auth is not set up in this project.",
|
|
13
|
+
"",
|
|
14
|
+
"Tenancy builds on your app's auth: it adds memberships to",
|
|
15
|
+
"`getCurrentUser`, and resolves the current organization from the",
|
|
16
|
+
"signed-in user's memberships. Set up auth first, then run this command",
|
|
17
|
+
"again:",
|
|
18
|
+
"",
|
|
19
|
+
` ${formatCedarCommand(["setup", "auth", "dbAuth"])}`,
|
|
20
|
+
"",
|
|
21
|
+
"Any provider works. See https://cedarjs.com/docs/authentication"
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
21
24
|
function noUserModelMessage(schemaPath) {
|
|
22
25
|
return [
|
|
23
26
|
`No \`User\` model found in ${schemaPath}.`,
|
|
@@ -30,7 +33,7 @@ function noUserModelMessage(schemaPath) {
|
|
|
30
33
|
"provider's id in a column beside it, so memberships keep working if you",
|
|
31
34
|
"switch providers or add a second one.",
|
|
32
35
|
"",
|
|
33
|
-
|
|
36
|
+
`Add a model like this one, run \`${formatCedarCommand(["prisma", "migrate", "dev"])}\`, then`,
|
|
34
37
|
"run this command again:",
|
|
35
38
|
"",
|
|
36
39
|
" model User {",
|
|
@@ -40,7 +43,7 @@ function noUserModelMessage(schemaPath) {
|
|
|
40
43
|
].join("\n");
|
|
41
44
|
}
|
|
42
45
|
export {
|
|
43
|
-
|
|
46
|
+
authNotSetUpMessage,
|
|
44
47
|
hasWebAuthFile,
|
|
45
48
|
noUserModelMessage
|
|
46
49
|
};
|
|
@@ -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
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
68
|
+
return {
|
|
69
|
+
schema: addDataMigrationModel(withTenancyModels),
|
|
70
|
+
outcome: modelsCustomized ? "forced" : "added"
|
|
71
|
+
};
|
|
68
72
|
}
|
|
69
73
|
export {
|
|
70
74
|
MEMBERSHIP_MODEL,
|
package/dist/commands/setup/tenancy/templates/ensureDefaultOrganizations.dataMigration.ts.template
CHANGED
|
@@ -10,8 +10,9 @@ const BATCH_SIZE = 100
|
|
|
10
10
|
* dbAuth, the signup handler). Every user without a membership gets one, in
|
|
11
11
|
* batches, using the same `ensureDefaultOrganization` new signups use.
|
|
12
12
|
*
|
|
13
|
-
* Run with `
|
|
14
|
-
* has applied the
|
|
13
|
+
* Run with `__DATA_MIGRATE_UP_COMMAND__`, after
|
|
14
|
+
* `__PRISMA_MIGRATE_DEV_COMMAND__` has applied the
|
|
15
|
+
* `Organization`/`Membership` migration.
|
|
15
16
|
*/
|
|
16
17
|
export default async ({ db }: { db: PrismaClient }) => {
|
|
17
18
|
let cursor: string | undefined
|
|
@@ -18,11 +18,16 @@ import { getPaths, transformTSToJS, writeFile } from "../../../lib/index.js";
|
|
|
18
18
|
import { isTypeScriptProject } from "../../../lib/project.js";
|
|
19
19
|
import { runTransform } from "../../../lib/runTransform.js";
|
|
20
20
|
import {
|
|
21
|
-
|
|
21
|
+
authNotSetUpMessage,
|
|
22
22
|
hasWebAuthFile,
|
|
23
23
|
noUserModelMessage
|
|
24
24
|
} from "./preflight.js";
|
|
25
|
-
import {
|
|
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) => {
|
|
@@ -61,7 +66,7 @@ const SIGNUP_SNIPPET = `
|
|
|
61
66
|
const handler = async ({ tenantField, force }) => {
|
|
62
67
|
const paths = getPaths();
|
|
63
68
|
if (!hasWebAuthFile(paths.web.src)) {
|
|
64
|
-
console.error(c.error(
|
|
69
|
+
console.error(c.error(authNotSetUpMessage()));
|
|
65
70
|
process.exit(1);
|
|
66
71
|
}
|
|
67
72
|
const preflightSchemaPath = await getSchemaPath(paths.api.prismaConfig);
|
|
@@ -82,16 +87,13 @@ const handler = async ({ tenantField, force }) => {
|
|
|
82
87
|
const graphqlPath = path.join(paths.api.functions, `graphql.${ext}`);
|
|
83
88
|
const functionsAuthPath = path.join(paths.api.functions, `auth.${ext}`);
|
|
84
89
|
const isDbAuthProject = fs.existsSync(functionsAuthPath) && fs.readFileSync(functionsAuthPath, "utf-8").includes("DbAuthHandler");
|
|
85
|
-
const writeTemplateFile = async (templateFile, outputPath) => {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
encoding: "utf8",
|
|
93
|
-
flag: "r"
|
|
94
|
-
});
|
|
90
|
+
const writeTemplateFile = async (templateFile, outputPath, replacements = {}) => {
|
|
91
|
+
const dirname = import.meta.dirname;
|
|
92
|
+
const templatePath = path.resolve(dirname, "templates", templateFile);
|
|
93
|
+
let templateContent = fs.readFileSync(templatePath, "utf-8");
|
|
94
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
95
|
+
templateContent = templateContent.replaceAll(token, value);
|
|
96
|
+
}
|
|
95
97
|
const content = projectIsTypescript ? templateContent : await transformTSToJS(outputPath, templateContent);
|
|
96
98
|
return writeFile(outputPath, content, { overwriteExisting: force });
|
|
97
99
|
};
|
|
@@ -107,27 +109,35 @@ const handler = async ({ tenantField, force }) => {
|
|
|
107
109
|
},
|
|
108
110
|
{
|
|
109
111
|
title: "Adding Organization and Membership models to schema.prisma...",
|
|
110
|
-
task: async () => {
|
|
112
|
+
task: async (ctx, task) => {
|
|
111
113
|
const schemaPath = await getSchemaPath(paths.api.prismaConfig);
|
|
112
114
|
const schema = fs.readFileSync(schemaPath, "utf-8");
|
|
113
|
-
let
|
|
115
|
+
let editResult;
|
|
114
116
|
try {
|
|
115
|
-
|
|
117
|
+
editResult = editSchema(schema, { force });
|
|
116
118
|
} catch (e) {
|
|
117
119
|
const message = e instanceof Error ? e.message : String(e);
|
|
118
120
|
if (message === "CEDAR_TENANCY_ERR_NO_USER_MODEL") {
|
|
121
|
+
const authSetup = formatCedarCommand(["setup", "auth", "dbAuth"]);
|
|
122
|
+
const tenancySetup = formatCedarCommand(["setup", "tenancy"]);
|
|
119
123
|
throw new Error(
|
|
120
|
-
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
if (message === "CEDAR_TENANCY_ERR_MODELS_EXIST") {
|
|
124
|
-
throw new Error(
|
|
125
|
-
"`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."
|
|
124
|
+
`No \`User\` model found in schema.prisma. Set up authentication first (e.g. \`${authSetup}\`) before running \`${tenancySetup}\`.`
|
|
126
125
|
);
|
|
127
126
|
}
|
|
128
127
|
throw e;
|
|
129
128
|
}
|
|
130
|
-
|
|
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
|
+
}
|
|
131
141
|
try {
|
|
132
142
|
await runTransitiveBin(
|
|
133
143
|
"prisma",
|
|
@@ -135,13 +145,21 @@ const handler = async ({ tenantField, force }) => {
|
|
|
135
145
|
{ cwd: paths.base }
|
|
136
146
|
);
|
|
137
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
|
+
}
|
|
138
152
|
throw new Error(
|
|
139
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."
|
|
140
154
|
);
|
|
141
155
|
}
|
|
142
156
|
const membershipsOccurrences = (schema2) => (schema2.match(/memberships\s+Membership\[\]/g) ?? []).length;
|
|
143
157
|
const formattedSchema = fs.readFileSync(schemaPath, "utf-8");
|
|
144
|
-
if (membershipsOccurrences(formattedSchema) <= membershipsOccurrences(
|
|
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
|
+
}
|
|
145
163
|
throw new Error(
|
|
146
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."
|
|
147
165
|
);
|
|
@@ -159,12 +177,13 @@ const handler = async ({ tenantField, force }) => {
|
|
|
159
177
|
});
|
|
160
178
|
if (transformResult.error) {
|
|
161
179
|
if (transformResult.error === "CEDAR_CODEMOD_ERR_OLD_FORMAT") {
|
|
180
|
+
const tenancySetup = formatCedarCommand(["setup", "tenancy"]);
|
|
162
181
|
throw new Error(
|
|
163
|
-
|
|
182
|
+
`It looks like your api/src/lib/db file is using the old format. Please update it as per the v8 upgrade guide, then run \`${tenancySetup}\` again.`
|
|
164
183
|
);
|
|
165
184
|
}
|
|
166
185
|
throw new Error(
|
|
167
|
-
"Could not add the tenancy Prisma extension. Please modify api/src/lib/db by hand: wrap the `db` export in `.$extends(createTenancyExtension({ models: { allExcept: ['user', 'organization', 'membership'] } }))`."
|
|
186
|
+
"Could not add the tenancy Prisma extension. Please modify `api/src/lib/db` by hand: wrap the `db` export in `.$extends(createTenancyExtension({ models: { allExcept: ['user', 'organization', 'membership'] } }))`."
|
|
168
187
|
);
|
|
169
188
|
}
|
|
170
189
|
}
|
|
@@ -271,7 +290,18 @@ ${SIGNUP_SNIPPET}`;
|
|
|
271
290
|
path.join(
|
|
272
291
|
dataMigrationsPath,
|
|
273
292
|
`${timestamp}-ensure-default-organizations.${ext}`
|
|
274
|
-
)
|
|
293
|
+
),
|
|
294
|
+
{
|
|
295
|
+
__DATA_MIGRATE_UP_COMMAND__: formatCedarCommand([
|
|
296
|
+
"data-migrate",
|
|
297
|
+
"up"
|
|
298
|
+
]),
|
|
299
|
+
__PRISMA_MIGRATE_DEV_COMMAND__: formatCedarCommand([
|
|
300
|
+
"prisma",
|
|
301
|
+
"migrate",
|
|
302
|
+
"dev"
|
|
303
|
+
])
|
|
304
|
+
}
|
|
275
305
|
);
|
|
276
306
|
}
|
|
277
307
|
},
|
|
@@ -329,17 +359,22 @@ ${SIGNUP_SNIPPET}`;
|
|
|
329
359
|
},
|
|
330
360
|
{
|
|
331
361
|
title: "Next steps...",
|
|
332
|
-
task: (
|
|
362
|
+
task: (ctx, task) => {
|
|
333
363
|
const migrateCommand = formatCedarCommand([
|
|
334
364
|
"prisma",
|
|
335
365
|
"migrate",
|
|
336
366
|
"dev"
|
|
337
367
|
]);
|
|
338
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
|
+
) : "";
|
|
339
374
|
task.title = `Next steps...
|
|
340
375
|
|
|
341
376
|
${c.success("\nMulti-tenancy configured!\n")}
|
|
342
|
-
|
|
377
|
+
${modelsExistWarning}
|
|
343
378
|
1. Run ${c.highlight(migrateCommand)} to create the Organization/Membership
|
|
344
379
|
tables, then ${c.highlight(dataMigrateCommand)} to give every existing
|
|
345
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.
|
|
3
|
+
"version": "7.0.0-canary.3108",
|
|
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.
|
|
40
|
-
"@cedarjs/babel-config": "7.0.0-canary.
|
|
41
|
-
"@cedarjs/cli-helpers": "7.0.0-canary.
|
|
42
|
-
"@cedarjs/internal": "7.0.0-canary.
|
|
43
|
-
"@cedarjs/prerender": "7.0.0-canary.
|
|
44
|
-
"@cedarjs/project-config": "7.0.0-canary.
|
|
45
|
-
"@cedarjs/structure": "7.0.0-canary.
|
|
46
|
-
"@cedarjs/telemetry": "7.0.0-canary.
|
|
47
|
-
"@cedarjs/utils": "7.0.0-canary.
|
|
48
|
-
"@cedarjs/vite": "7.0.0-canary.
|
|
49
|
-
"@cedarjs/web-server": "7.0.0-canary.
|
|
39
|
+
"@cedarjs/api-server": "7.0.0-canary.3108",
|
|
40
|
+
"@cedarjs/babel-config": "7.0.0-canary.3108",
|
|
41
|
+
"@cedarjs/cli-helpers": "7.0.0-canary.3108",
|
|
42
|
+
"@cedarjs/internal": "7.0.0-canary.3108",
|
|
43
|
+
"@cedarjs/prerender": "7.0.0-canary.3108",
|
|
44
|
+
"@cedarjs/project-config": "7.0.0-canary.3108",
|
|
45
|
+
"@cedarjs/structure": "7.0.0-canary.3108",
|
|
46
|
+
"@cedarjs/telemetry": "7.0.0-canary.3108",
|
|
47
|
+
"@cedarjs/utils": "7.0.0-canary.3108",
|
|
48
|
+
"@cedarjs/vite": "7.0.0-canary.3108",
|
|
49
|
+
"@cedarjs/web-server": "7.0.0-canary.3108",
|
|
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.
|
|
97
|
+
"@cedarjs/framework-tools": "7.0.0-canary.3108",
|
|
98
98
|
"@prisma/dmmf": "7.10.0",
|
|
99
99
|
"@types/archiver": "^7.0.0",
|
|
100
100
|
"memfs": "4.68.2",
|