@cedarjs/cli 9.0.0-canary.1784 → 9.0.0-canary.3124

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.
Files changed (277) hide show
  1. package/README.md +7 -10
  2. package/dist/cfw.js +10 -6
  3. package/dist/commands/build/buildHandler.js +220 -45
  4. package/dist/commands/build/buildPackagesTask.js +21 -20
  5. package/dist/commands/build.js +13 -2
  6. package/dist/commands/check.js +4 -6
  7. package/dist/commands/console.js +6 -4
  8. package/dist/commands/deploy/baremetal/SshExecutor.js +13 -4
  9. package/dist/commands/deploy/baremetal/baremetalHandler.js +116 -50
  10. package/dist/commands/deploy/baremetal.js +11 -4
  11. package/dist/commands/deploy/flightcontrolHandler.js +28 -15
  12. package/dist/commands/deploy/helpers/deployHandler.js +11 -6
  13. package/dist/commands/deploy/netlify.js +1 -1
  14. package/dist/commands/deploy/packing/nft.js +1 -2
  15. package/dist/commands/deploy/render.js +1 -1
  16. package/dist/commands/deploy/renderHandler.js +24 -13
  17. package/dist/commands/deploy/serverlessHandler.js +69 -27
  18. package/dist/commands/deploy/vercel.js +1 -1
  19. package/dist/commands/destroy/component/component.js +2 -3
  20. package/dist/commands/destroy/directive/directive.js +1 -3
  21. package/dist/commands/destroy/directive/directiveHandler.js +2 -4
  22. package/dist/commands/destroy/function/function.js +1 -3
  23. package/dist/commands/destroy/handlerHelpers.js +17 -17
  24. package/dist/commands/destroy/helpers.js +8 -2
  25. package/dist/commands/destroy/layout/layout.js +1 -2
  26. package/dist/commands/destroy/page/pageHandler.js +3 -3
  27. package/dist/commands/destroy/scaffold/scaffoldHandler.js +31 -12
  28. package/dist/commands/destroy/sdl/sdlHandler.js +3 -3
  29. package/dist/commands/dev/apiDebugFlag.js +5 -4
  30. package/dist/commands/dev/devHandler.js +219 -68
  31. package/dist/commands/dev/packageWatchCommands.js +8 -3
  32. package/dist/commands/dev.js +16 -6
  33. package/dist/commands/execHandler.js +49 -29
  34. package/dist/commands/experimental/live-queries/liveQueries.js +32 -0
  35. package/dist/commands/experimental/live-queries/liveQueriesHandler.js +448 -0
  36. package/dist/commands/experimental/live-queries/templates/liveQueriesListener.ts.template +153 -0
  37. package/dist/commands/experimental/live-queries/templates/migration.sql.template +100 -0
  38. package/dist/commands/experimental/setupInngest.js +2 -2
  39. package/dist/commands/experimental/setupInngestHandler.js +13 -12
  40. package/dist/commands/experimental/setupOpentelemetry.js +2 -2
  41. package/dist/commands/experimental/setupOpentelemetryHandler.js +14 -10
  42. package/dist/commands/experimental/setupReactCompiler.js +2 -2
  43. package/dist/commands/experimental/setupReactCompilerHandler.js +48 -33
  44. package/dist/commands/experimental/setupRsc.js +1 -1
  45. package/dist/commands/experimental/setupRscHandler.js +20 -16
  46. package/dist/commands/experimental/setupStreamingSsr.js +2 -2
  47. package/dist/commands/experimental/setupStreamingSsrHandler.js +49 -34
  48. package/dist/commands/experimental/templates/opentelemetry.ts.template +2 -1
  49. package/dist/commands/experimental/templates/rsc/Document.tsx.template +1 -1
  50. package/dist/commands/experimental/templates/rsc/entry.client.tsx.template +8 -8
  51. package/dist/commands/experimental/templates/streamingSsr/Document.tsx.template +1 -1
  52. package/dist/commands/experimental/templates/streamingSsr/entry.client.tsx.template +6 -6
  53. package/dist/commands/experimental/templates/streamingSsr/tsconfig.json.template +6 -6
  54. package/dist/commands/experimental/util.js +27 -25
  55. package/dist/commands/experimental.js +3 -2
  56. package/dist/commands/generate/cell/cell.js +20 -0
  57. package/dist/commands/generate/cell/cellHandler.js +89 -40
  58. package/dist/commands/generate/cell/templates/cell.tsx.template +21 -2
  59. package/dist/commands/generate/cell/templates/cellFragment.tsx.template +43 -0
  60. package/dist/commands/generate/cell/templates/cellList.tsx.template +21 -2
  61. package/dist/commands/generate/cell/templates/storiesFragment.tsx.template +23 -0
  62. package/dist/commands/generate/cell/templates/test.js.template +1 -1
  63. package/dist/commands/generate/cell/templates/testFragment.js.template +30 -0
  64. package/dist/commands/generate/component/componentHandler.js +14 -16
  65. package/dist/commands/generate/dataMigration/dataMigration.js +36 -18
  66. package/dist/commands/generate/dataMigration/templates/dataMigration.js.template +1 -1
  67. package/dist/commands/generate/dataMigration/templates/dataMigration.ts.template +1 -1
  68. package/dist/commands/generate/dbAuth/dbAuth.js +1 -0
  69. package/dist/commands/generate/dbAuth/dbAuthHandler.js +63 -34
  70. package/dist/commands/generate/dbAuth/templates/login.oauth.tsx.template +164 -0
  71. package/dist/commands/generate/dbAuth/templates/signup.oauth.tsx.template +147 -0
  72. package/dist/commands/generate/directive/directiveHandler.js +14 -16
  73. package/dist/commands/generate/function/function.js +1 -0
  74. package/dist/commands/generate/function/functionHandler.js +14 -19
  75. package/dist/commands/generate/helpers.js +34 -18
  76. package/dist/commands/generate/job/job.js +1 -0
  77. package/dist/commands/generate/job/jobHandler.js +20 -24
  78. package/dist/commands/generate/layout/layoutHandler.js +13 -16
  79. package/dist/commands/generate/model/model.js +1 -0
  80. package/dist/commands/generate/model/modelHandler.js +11 -5
  81. package/dist/commands/generate/ogImage/ogImage.js +1 -0
  82. package/dist/commands/generate/ogImage/ogImageHandler.js +18 -11
  83. package/dist/commands/generate/package/filesTask.js +9 -0
  84. package/dist/commands/generate/package/packageHandler.js +50 -61
  85. package/dist/commands/generate/package/templates/test.ts.template +2 -2
  86. package/dist/commands/generate/package/templates/vitest.config.ts.template +9 -0
  87. package/dist/commands/generate/page/pageHandler.js +27 -24
  88. package/dist/commands/generate/realtime/realtime.js +2 -2
  89. package/dist/commands/generate/realtime/realtimeHandler.js +51 -32
  90. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.ts.template +1 -1
  91. package/dist/commands/generate/scaffold/scaffold.js +1 -0
  92. package/dist/commands/generate/scaffold/scaffoldHandler.js +176 -49
  93. package/dist/commands/generate/scaffold/templates/lib/formatters.tsx.template +1 -1
  94. package/dist/commands/generate/script/script.js +1 -0
  95. package/dist/commands/generate/script/scriptHandler.js +10 -6
  96. package/dist/commands/generate/script/templates/script.ts.template +9 -4
  97. package/dist/commands/generate/script/templates/tsconfig.json.template +16 -2
  98. package/dist/commands/generate/sdl/sdl.js +4 -3
  99. package/dist/commands/generate/sdl/sdlHandler.js +135 -46
  100. package/dist/commands/generate/sdl/stubFiles.js +132 -0
  101. package/dist/commands/generate/sdl/templates/sdl.js.template +2 -1
  102. package/dist/commands/generate/sdl/templates/sdl.ts.template +2 -1
  103. package/dist/commands/generate/secret/secret.js +20 -20
  104. package/dist/commands/generate/service/service.js +5 -5
  105. package/dist/commands/generate/service/serviceHandler.js +126 -61
  106. package/dist/commands/generate/service/templates/scenarios.ts.template +4 -2
  107. package/dist/commands/generate/service/templates/service.ts.template +22 -8
  108. package/dist/commands/generate/service/templates/test.ts.template +2 -2
  109. package/dist/commands/generate/yargsCommandHelpers.js +7 -2
  110. package/dist/commands/generate/yargsHandlerHelpers.js +20 -18
  111. package/dist/commands/generate.js +4 -4
  112. package/dist/commands/info.js +2 -2
  113. package/dist/commands/jobsHandler.js +7 -4
  114. package/dist/commands/lint.js +26 -69
  115. package/dist/commands/prerenderHandler.js +95 -58
  116. package/dist/commands/prismaHandler.js +51 -26
  117. package/dist/commands/record/init.js +2 -2
  118. package/dist/commands/record.js +4 -3
  119. package/dist/commands/serve.js +245 -31
  120. package/dist/commands/serveApiHandler.js +7 -6
  121. package/dist/commands/serveBothHandler.js +22 -13
  122. package/dist/commands/serveWebHandler.js +3 -2
  123. package/dist/commands/setup/auth/auth.js +15 -11
  124. package/dist/commands/setup/cache/cache.js +1 -1
  125. package/dist/commands/setup/cache/cacheHandler.js +15 -9
  126. package/dist/commands/setup/database/database.js +15 -0
  127. package/dist/commands/setup/database/postgres.js +19 -0
  128. package/dist/commands/setup/database/postgresHandler.js +196 -0
  129. package/dist/commands/setup/database/templates/db.ts.template +32 -0
  130. package/dist/commands/setup/deploy/deploy.js +4 -3
  131. package/dist/commands/setup/deploy/helpers/helpers.js +5 -1
  132. package/dist/commands/setup/deploy/helpers/index.js +132 -6
  133. package/dist/commands/setup/deploy/providers/baremetalHandler.js +28 -6
  134. package/dist/commands/setup/deploy/providers/coherence.js +1 -1
  135. package/dist/commands/setup/deploy/providers/coherenceHandler.js +60 -31
  136. package/dist/commands/setup/deploy/providers/flightcontrolHandler.js +69 -51
  137. package/dist/commands/setup/deploy/providers/netlify.js +6 -0
  138. package/dist/commands/setup/deploy/providers/netlifyHandler.js +114 -13
  139. package/dist/commands/setup/deploy/providers/renderHandler.js +44 -41
  140. package/dist/commands/setup/deploy/providers/serverlessHandler.js +7 -5
  141. package/dist/commands/setup/deploy/providers/universal-deploy.js +11 -0
  142. package/dist/commands/setup/deploy/providers/universalDeployHandler.js +186 -0
  143. package/dist/commands/setup/deploy/providers/vercel.js +6 -0
  144. package/dist/commands/setup/deploy/providers/vercelHandler.js +115 -14
  145. package/dist/commands/setup/deploy/templates/baremetal.js +3 -2
  146. package/dist/commands/setup/deploy/templates/flightcontrol.js +60 -49
  147. package/dist/commands/setup/deploy/templates/netlify.js +2 -1
  148. package/dist/commands/setup/deploy/templates/netlifyUD.js +31 -0
  149. package/dist/commands/setup/deploy/templates/render.js +32 -17
  150. package/dist/commands/setup/docker/docker.js +2 -2
  151. package/dist/commands/setup/docker/dockerHandler.js +74 -39
  152. package/dist/commands/setup/docker/templates/Dockerfile.npm +119 -0
  153. package/dist/commands/setup/docker/templates/Dockerfile.pnpm +112 -0
  154. package/dist/commands/setup/docker/templates/{Dockerfile → Dockerfile.yarn} +4 -7
  155. package/dist/commands/setup/docker/templates/docker-compose.dev.yml +3 -3
  156. package/dist/commands/setup/docker/templates/docker-compose.prod.yml +2 -5
  157. package/dist/commands/setup/docker/templates/dockerignore +1 -0
  158. package/dist/commands/setup/generator/generator.js +1 -1
  159. package/dist/commands/setup/generator/generatorHandler.js +5 -4
  160. package/dist/commands/setup/graphql/features/fragments/appGqlConfigTransform.js +6 -3
  161. package/dist/commands/setup/graphql/features/fragments/fragments.js +1 -1
  162. package/dist/commands/setup/graphql/features/fragments/fragmentsHandler.js +5 -7
  163. package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocuments.js +1 -1
  164. package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocumentsHandler.js +4 -3
  165. package/dist/commands/setup/i18n/i18n.js +1 -1
  166. package/dist/commands/setup/i18n/i18nHandler.js +21 -17
  167. package/dist/commands/setup/jobs/jobs.js +1 -1
  168. package/dist/commands/setup/jobs/jobsHandler.js +17 -12
  169. package/dist/commands/setup/mailer/mailer.js +1 -1
  170. package/dist/commands/setup/mailer/mailerHandler.js +13 -11
  171. package/dist/commands/setup/middleware/ogImage/ogImage.js +1 -1
  172. package/dist/commands/setup/middleware/ogImage/ogImageHandler.js +2 -1
  173. package/dist/commands/setup/monitoring/sentry/sentry.js +1 -1
  174. package/dist/commands/setup/monitoring/sentry/sentryHandler.js +18 -12
  175. package/dist/commands/setup/neon/neon.js +35 -0
  176. package/dist/commands/setup/neon/neonHandler.js +264 -0
  177. package/dist/commands/setup/package/package.js +1 -1
  178. package/dist/commands/setup/package/packageHandler.js +18 -14
  179. package/dist/commands/setup/realtime/addRealtimeToGraphql.js +1 -1
  180. package/dist/commands/setup/realtime/realtime.js +1 -1
  181. package/dist/commands/setup/realtime/realtimeHandler.js +26 -37
  182. package/dist/commands/setup/realtime/templates/realtime.ts.template +1 -1
  183. package/dist/commands/setup/realtime/templates/subscriptions/countdown/countdown.ts.template +1 -1
  184. package/dist/commands/setup/realtime/templates/subscriptions/newMessage/newMessage.ts.template +1 -1
  185. package/dist/commands/setup/server-file/serverFile.js +1 -1
  186. package/dist/commands/setup/server-file/serverFileHandler.js +18 -10
  187. package/dist/commands/setup/tenancy/authCodemod.js +66 -0
  188. package/dist/commands/setup/tenancy/dbCodemod.js +64 -0
  189. package/dist/commands/setup/tenancy/graphqlCodemod.js +88 -0
  190. package/dist/commands/setup/tenancy/preflight.js +49 -0
  191. package/dist/commands/setup/tenancy/schemaPrisma.js +81 -0
  192. package/dist/commands/setup/tenancy/signupCodemod.js +153 -0
  193. package/dist/commands/setup/tenancy/templates/InvitePage.tsx.template +63 -0
  194. package/dist/commands/setup/tenancy/templates/OrgScope.tsx.template +40 -0
  195. package/dist/commands/setup/tenancy/templates/ensureDefaultOrganizations.dataMigration.ts.template +39 -0
  196. package/dist/commands/setup/tenancy/templates/organizations.scenarios.ts.template +59 -0
  197. package/dist/commands/setup/tenancy/templates/organizations.sdl.ts.template +89 -0
  198. package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +389 -0
  199. package/dist/commands/setup/tenancy/templates/organizations.ts.template +394 -0
  200. package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +35 -0
  201. package/dist/commands/setup/tenancy/templates/requireMembership.ts.template +25 -0
  202. package/dist/commands/setup/tenancy/tenancy.js +30 -0
  203. package/dist/commands/setup/tenancy/tenancyHandler.js +425 -0
  204. package/dist/commands/setup/ui/helpers/helpers.js +5 -1
  205. package/dist/commands/setup/ui/libraries/chakra-uiHandler.js +16 -8
  206. package/dist/commands/setup/ui/libraries/mantineHandler.js +26 -16
  207. package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +40 -29
  208. package/dist/commands/setup/uploads/schemaPrisma.js +41 -0
  209. package/dist/commands/setup/uploads/serverFile.js +151 -0
  210. package/dist/commands/setup/uploads/templates/requireUploadToken.ts.template +15 -0
  211. package/dist/commands/setup/uploads/templates/srcLibUploads.ts.template +23 -24
  212. package/dist/commands/setup/uploads/templates/uploads.sdl.ts.template +79 -0
  213. package/dist/commands/setup/uploads/templates/uploads.ts.template +89 -0
  214. package/dist/commands/setup/uploads/templates/withDataUri.ts.template +14 -0
  215. package/dist/commands/setup/uploads/templates/withSignedUrl.ts.template +16 -0
  216. package/dist/commands/setup/uploads/uploads.js +49 -7
  217. package/dist/commands/setup/uploads/uploadsHandler.js +266 -72
  218. package/dist/commands/setup.js +5 -4
  219. package/dist/commands/studio.js +1 -1
  220. package/dist/commands/studioHandler.js +2 -2
  221. package/dist/commands/test/datasourceWarning.js +42 -0
  222. package/dist/commands/test/testHandler.js +104 -0
  223. package/dist/commands/test/viteVersionCheck.js +75 -0
  224. package/dist/commands/test.js +16 -19
  225. package/dist/commands/type-check.js +2 -1
  226. package/dist/commands/type-checkHandler.js +25 -18
  227. package/dist/commands/upgrade/preUpgradeScripts.js +11 -11
  228. package/dist/commands/upgrade/upgrade.js +4 -2
  229. package/dist/commands/upgrade/upgradeHandler.js +97 -73
  230. package/dist/index.js +15 -11
  231. package/dist/lib/background.js +22 -1
  232. package/dist/lib/configureStorybook.js +2 -2
  233. package/dist/lib/exec.js +68 -63
  234. package/dist/lib/exit.js +20 -5
  235. package/dist/lib/extendFile.js +23 -13
  236. package/dist/lib/generatePrismaClient.js +48 -12
  237. package/dist/lib/index.js +137 -106
  238. package/dist/lib/loadEnvFiles.js +1 -1
  239. package/dist/lib/locking.js +8 -4
  240. package/dist/lib/merge/algorithms.js +4 -1
  241. package/dist/lib/merge/index.js +78 -41
  242. package/dist/lib/merge/semanticIdentity.js +18 -6
  243. package/dist/lib/merge/strategy.js +86 -36
  244. package/dist/lib/packages.js +23 -13
  245. package/dist/lib/plugin.js +13 -7
  246. package/dist/lib/pluralHelpers.js +6 -2
  247. package/dist/lib/project.js +3 -1
  248. package/dist/lib/rollback.js +7 -7
  249. package/dist/lib/schemaHelpers.js +16 -19
  250. package/dist/lib/test.js +20 -5
  251. package/dist/lib/updateCheck.js +47 -18
  252. package/dist/middleware/checkNodeVersion.js +9 -4
  253. package/dist/middleware/{detectProjectRxVersion.js → detectProjectCedarVersion.js} +2 -2
  254. package/dist/plugin.js +18 -12
  255. package/dist/telemetry/exporter.js +12 -5
  256. package/dist/telemetry/index.js +4 -3
  257. package/dist/telemetry/resource.js +9 -12
  258. package/dist/telemetry/send.js +14 -10
  259. package/dist/testLib/cells.js +5 -3
  260. package/package.json +44 -41
  261. package/LICENSE +0 -21
  262. package/dist/commands/consoleHandler.js +0 -68
  263. package/dist/commands/setup/tsconfig/tsconfig.js +0 -25
  264. package/dist/commands/setup/tsconfig/tsconfigHandler.js +0 -59
  265. package/dist/commands/setup/uploads/dbCodemod.js +0 -28
  266. package/dist/commands/setup/uploads/templates/signedUrl.ts.template +0 -21
  267. package/dist/commands/setup/vite/templates/vite.config.ts.template +0 -19
  268. package/dist/commands/setup/vite/vite.js +0 -38
  269. package/dist/commands/setup/vite/viteHandler.js +0 -83
  270. package/dist/commands/testEsm.js +0 -36
  271. package/dist/commands/testHandler.js +0 -137
  272. package/dist/commands/testHandlerEsm.js +0 -85
  273. package/dist/commands/ts-to-js.js +0 -20
  274. package/dist/lib/cedarPluralize.js +0 -56
  275. package/dist/lib/colors.js +0 -18
  276. package/dist/testLib/getFilesWithPattern.js +0 -21
  277. /package/dist/{index.d.js → global.d.js} +0 -0
@@ -0,0 +1,66 @@
1
+ import j from "jscodeshift";
2
+ function buildMembershipsSelectProperty() {
3
+ const snippet = `
4
+ const __tenancySnippet = {
5
+ memberships: {
6
+ select: {
7
+ id: true,
8
+ organizationId: true,
9
+ role: true,
10
+ organization: { select: { id: true, slug: true, name: true } },
11
+ },
12
+ },
13
+ }
14
+ `;
15
+ const property = j.withParser("ts")(snippet).find(j.ObjectProperty, { key: { name: "memberships" } }).nodes()[0];
16
+ if (!property) {
17
+ throw new Error("CEDAR_CODEMOD_ERR_AUTH_SHAPE_NOT_FOUND");
18
+ }
19
+ return property;
20
+ }
21
+ function transform(fileInfo) {
22
+ const root = j.withParser("tsx")(fileInfo.source);
23
+ let selectFound = false;
24
+ root.find(j.CallExpression, {
25
+ callee: {
26
+ type: "MemberExpression",
27
+ object: {
28
+ type: "MemberExpression",
29
+ object: { name: "db" },
30
+ property: { name: "user" }
31
+ },
32
+ property: { name: "findUnique" }
33
+ }
34
+ }).forEach((path) => {
35
+ const [args] = path.node.arguments;
36
+ if (!j.ObjectExpression.check(args)) {
37
+ return;
38
+ }
39
+ const selectProperty = args.properties.find(
40
+ (prop) => j.ObjectProperty.check(prop) && j.Identifier.check(prop.key) && prop.key.name === "select"
41
+ );
42
+ if (!selectProperty || !j.ObjectProperty.check(selectProperty) || !j.ObjectExpression.check(selectProperty.value)) {
43
+ return;
44
+ }
45
+ const alreadyHasMemberships = selectProperty.value.properties.some(
46
+ (prop) => j.ObjectProperty.check(prop) && j.Identifier.check(prop.key) && prop.key.name === "memberships"
47
+ );
48
+ if (!alreadyHasMemberships) {
49
+ selectProperty.value.properties.push(buildMembershipsSelectProperty());
50
+ }
51
+ selectFound = true;
52
+ });
53
+ if (!selectFound) {
54
+ throw new Error("CEDAR_CODEMOD_ERR_AUTH_SHAPE_NOT_FOUND");
55
+ }
56
+ const tenancyExportSnippet = `export { hasOrgRole, requireMembership } from '@cedarjs/tenancy'
57
+ `;
58
+ const tenancyExport = j.withParser("ts")(tenancyExportSnippet).find(j.ExportNamedDeclaration).nodes()[0];
59
+ root.find(j.Program).forEach((path) => {
60
+ path.node.body.push(tenancyExport);
61
+ });
62
+ return root.toSource();
63
+ }
64
+ export {
65
+ transform as default
66
+ };
@@ -0,0 +1,64 @@
1
+ import j from "jscodeshift";
2
+ const DEFAULT_GLOBAL_MODELS = ["user", "organization", "membership"];
3
+ function transform(fileInfo, _api, options) {
4
+ const root = j.withParser("tsx")(fileInfo.source);
5
+ const tenantField = typeof options.tenantField === "string" ? options.tenantField : void 0;
6
+ const imports = root.find(j.ImportDeclaration);
7
+ imports.at(-1).insertAfter(
8
+ j.importDeclaration(
9
+ [j.importSpecifier(j.identifier("createTenancyExtension"))],
10
+ j.literal("@cedarjs/tenancy")
11
+ )
12
+ );
13
+ const configProperties = [
14
+ ...tenantField ? [
15
+ j.objectProperty(
16
+ j.identifier("tenantField"),
17
+ j.stringLiteral(tenantField)
18
+ )
19
+ ] : [],
20
+ j.objectProperty(
21
+ j.identifier("models"),
22
+ j.objectExpression([
23
+ j.objectProperty(
24
+ j.identifier("allExcept"),
25
+ j.arrayExpression(
26
+ DEFAULT_GLOBAL_MODELS.map((name) => j.stringLiteral(name))
27
+ )
28
+ )
29
+ ])
30
+ )
31
+ ];
32
+ let found = false;
33
+ root.find(j.VariableDeclaration, { declarations: [{ id: { name: "db" } }] }).forEach((path) => {
34
+ const dbDeclaration = path.node.declarations[0];
35
+ if (!j.VariableDeclarator.check(dbDeclaration)) {
36
+ return;
37
+ }
38
+ if (j.NewExpression.check(dbDeclaration.init)) {
39
+ throw new Error("CEDAR_CODEMOD_ERR_OLD_FORMAT");
40
+ }
41
+ if (!j.Expression.check(dbDeclaration.init)) {
42
+ return;
43
+ }
44
+ found = true;
45
+ const extensionCall = j.callExpression(
46
+ j.identifier("createTenancyExtension"),
47
+ [j.objectExpression(configProperties)]
48
+ );
49
+ dbDeclaration.init = j.callExpression(
50
+ j.memberExpression(dbDeclaration.init, j.identifier("$extends")),
51
+ [extensionCall]
52
+ );
53
+ });
54
+ if (!found) {
55
+ throw new Error("CEDAR_CODEMOD_ERR_DB_SHAPE_NOT_FOUND");
56
+ }
57
+ return root.toSource().replace(
58
+ "createTenancyExtension(",
59
+ "createTenancyExtension<typeof prismaClient>("
60
+ );
61
+ }
62
+ export {
63
+ transform as default
64
+ };
@@ -0,0 +1,88 @@
1
+ import j from "jscodeshift";
2
+ function buildContextProperty() {
3
+ const snippet = `
4
+ const __tenancySnippet = {
5
+ context: async ({ context: gqlContext }) => {
6
+ const { currentUser, event, request, params } = gqlContext
7
+
8
+ // A user whose getCurrentUser result doesn't carry memberships
9
+ // can't be matched to an organization, so the request runs with
10
+ // no current organization set.
11
+ if (!isUserWithMemberships(currentUser)) {
12
+ return {}
13
+ }
14
+
15
+ // Yoga can populate either \`event\` (Lambda-style deployments) or
16
+ // \`request\` (Fetch-API-style deployments); resolveCurrentOrg reads
17
+ // the organization header off either.
18
+ const requestEvent = event ?? request
19
+
20
+ if (!requestEvent) {
21
+ return {}
22
+ }
23
+
24
+ const currentOrg = await resolveCurrentOrg({
25
+ event: requestEvent,
26
+ variables: params.variables,
27
+ currentUser,
28
+ lookupOrg: async (idOrSlug) =>
29
+ (await db.organization.findUnique({
30
+ where: { id: idOrSlug },
31
+ select: { id: true, slug: true },
32
+ })) ??
33
+ db.organization.findUnique({
34
+ where: { slug: idOrSlug },
35
+ select: { id: true, slug: true },
36
+ }),
37
+ })
38
+
39
+ return { currentOrg }
40
+ },
41
+ }
42
+ `;
43
+ const property = j.withParser("ts")(snippet).find(j.ObjectProperty, { key: { name: "context" } }).nodes()[0];
44
+ if (!property) {
45
+ throw new Error("CEDAR_CODEMOD_ERR_GRAPHQL_HANDLER_NOT_FOUND");
46
+ }
47
+ return property;
48
+ }
49
+ function addNamedImport(root, names, source) {
50
+ const imports = root.find(j.ImportDeclaration);
51
+ imports.at(-1).insertAfter(
52
+ j.importDeclaration(
53
+ names.map((name) => j.importSpecifier(j.identifier(name))),
54
+ j.literal(source)
55
+ )
56
+ );
57
+ }
58
+ function transform(fileInfo) {
59
+ const root = j.withParser("tsx")(fileInfo.source);
60
+ const handlerCalls = root.find(j.CallExpression, {
61
+ callee: { name: "createGraphQLHandler" }
62
+ });
63
+ if (handlerCalls.length === 0) {
64
+ throw new Error("CEDAR_CODEMOD_ERR_GRAPHQL_HANDLER_NOT_FOUND");
65
+ }
66
+ handlerCalls.forEach((path) => {
67
+ const [config] = path.node.arguments;
68
+ if (!j.ObjectExpression.check(config)) {
69
+ throw new Error("CEDAR_CODEMOD_ERR_GRAPHQL_HANDLER_NOT_FOUND");
70
+ }
71
+ const hasContext = config.properties.some(
72
+ (prop) => j.ObjectProperty.check(prop) && j.Identifier.check(prop.key) && prop.key.name === "context"
73
+ );
74
+ if (hasContext) {
75
+ throw new Error("CEDAR_CODEMOD_ERR_GRAPHQL_CONTEXT_EXISTS");
76
+ }
77
+ config.properties.push(buildContextProperty());
78
+ });
79
+ addNamedImport(
80
+ root,
81
+ ["isUserWithMemberships", "resolveCurrentOrg"],
82
+ "@cedarjs/tenancy"
83
+ );
84
+ return root.toSource();
85
+ }
86
+ export {
87
+ transform as default
88
+ };
@@ -0,0 +1,49 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
4
+ const WEB_AUTH_EXTENSIONS = ["ts", "tsx", "js", "jsx"];
5
+ function hasWebAuthFile(webSrcPath) {
6
+ return WEB_AUTH_EXTENSIONS.some(
7
+ (extension) => fs.existsSync(path.join(webSrcPath, `auth.${extension}`))
8
+ );
9
+ }
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
+ }
24
+ function noUserModelMessage(schemaPath) {
25
+ return [
26
+ `No \`User\` model found in ${schemaPath}.`,
27
+ "",
28
+ "Tenancy hangs memberships off your app's `User` model, so it needs one",
29
+ "before it can add `Organization` and `Membership`.",
30
+ "",
31
+ "dbAuth adds this model for you. Providers that authenticate against an",
32
+ "external service leave it to you: give `User` its own id and store the",
33
+ "provider's id in a column beside it, so memberships keep working if you",
34
+ "switch providers or add a second one.",
35
+ "",
36
+ `Add a model like this one, run \`${formatCedarCommand(["prisma", "migrate", "dev"])}\`, then`,
37
+ "run this command again:",
38
+ "",
39
+ " model User {",
40
+ " id String @id @default(cuid())",
41
+ " email String @unique",
42
+ " }"
43
+ ].join("\n");
44
+ }
45
+ export {
46
+ authNotSetUpMessage,
47
+ hasWebAuthFile,
48
+ noUserModelMessage
49
+ };
@@ -0,0 +1,81 @@
1
+ const ORGANIZATION_MODEL = `model Organization {
2
+ id String @id @default(cuid())
3
+ name String
4
+ slug String @unique
5
+ createdAt DateTime @default(now())
6
+ updatedAt DateTime @updatedAt
7
+ memberships Membership[]
8
+ }`;
9
+ const MEMBERSHIP_MODEL = `model Membership {
10
+ id String @id @default(cuid())
11
+ role String
12
+ // Null while the membership is a pending invitation.
13
+ userId String?
14
+ organizationId String
15
+ invitedById String?
16
+ invitationToken String? @unique
17
+ createdAt DateTime @default(now())
18
+ updatedAt DateTime @updatedAt
19
+ user User? @relation(fields: [userId], references: [id])
20
+ organization Organization @relation(fields: [organizationId], references: [id])
21
+ invitedBy Membership? @relation("Inviter", fields: [invitedById], references: [id])
22
+ invitees Membership[] @relation("Inviter")
23
+
24
+ @@unique([userId, organizationId])
25
+ @@index([organizationId])
26
+ }`;
27
+ const RW_DATA_MIGRATION_MODEL = `model RW_DataMigration {
28
+ version String @id
29
+ name String
30
+ startedAt DateTime
31
+ finishedAt DateTime
32
+ }`;
33
+ function hasModel(schema, modelName) {
34
+ return new RegExp(`\\bmodel\\s+${modelName}\\s*\\{`).test(schema);
35
+ }
36
+ function addTenancyModels(schema) {
37
+ return [ORGANIZATION_MODEL, MEMBERSHIP_MODEL].reduce(appendModel, schema);
38
+ }
39
+ function appendModel(schema, model) {
40
+ if (schema.includes(model)) {
41
+ return schema;
42
+ }
43
+ return `${schema.trimEnd()}
44
+
45
+ ${model}
46
+ `;
47
+ }
48
+ function addDataMigrationModel(schema) {
49
+ if (hasModel(schema, "RW_DataMigration")) {
50
+ return schema;
51
+ }
52
+ return `${schema.trimEnd()}
53
+
54
+ ${RW_DATA_MIGRATION_MODEL}
55
+ `;
56
+ }
57
+ function editSchema(schema, options) {
58
+ if (!hasModel(schema, "User")) {
59
+ throw new Error("CEDAR_TENANCY_ERR_NO_USER_MODEL");
60
+ }
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" };
66
+ }
67
+ const withTenancyModels = addTenancyModels(schema);
68
+ return {
69
+ schema: addDataMigrationModel(withTenancyModels),
70
+ outcome: modelsCustomized ? "forced" : "added"
71
+ };
72
+ }
73
+ export {
74
+ MEMBERSHIP_MODEL,
75
+ ORGANIZATION_MODEL,
76
+ RW_DATA_MIGRATION_MODEL,
77
+ addDataMigrationModel,
78
+ addTenancyModels,
79
+ editSchema,
80
+ hasModel
81
+ };
@@ -0,0 +1,153 @@
1
+ import j from "jscodeshift";
2
+ function useShorthandUserAttributes(fn) {
3
+ const [firstParam] = fn.params;
4
+ if (!j.ObjectPattern.check(firstParam)) {
5
+ throw new Error("CEDAR_CODEMOD_ERR_SIGNUP_SHAPE_NOT_FOUND");
6
+ }
7
+ const userAttributesProperty = firstParam.properties.find(
8
+ (prop) => j.ObjectProperty.check(prop) && j.Identifier.check(prop.key) && prop.key.name === "userAttributes"
9
+ );
10
+ if (!userAttributesProperty || !j.ObjectProperty.check(userAttributesProperty)) {
11
+ throw new Error("CEDAR_CODEMOD_ERR_SIGNUP_SHAPE_NOT_FOUND");
12
+ }
13
+ userAttributesProperty.value = j.identifier("userAttributes");
14
+ userAttributesProperty.shorthand = true;
15
+ }
16
+ function addInvitationTokenToUserAttributes(root) {
17
+ root.find(j.TSInterfaceDeclaration, { id: { name: "UserAttributes" } }).forEach((path) => {
18
+ const alreadyHasField = path.node.body.body.some(
19
+ (member) => j.TSPropertySignature.check(member) && j.Identifier.check(member.key) && member.key.name === "invitationToken"
20
+ );
21
+ if (alreadyHasField) {
22
+ return;
23
+ }
24
+ path.node.body.body.push(
25
+ j.tsPropertySignature(
26
+ j.identifier("invitationToken"),
27
+ j.tsTypeAnnotation(j.tsStringKeyword()),
28
+ true
29
+ // optional
30
+ )
31
+ );
32
+ });
33
+ }
34
+ function wrapCreateCall(fn) {
35
+ fn.async = true;
36
+ const ensureCallStatement = j.expressionStatement(
37
+ j.awaitExpression(
38
+ j.callExpression(j.identifier("ensureDefaultOrganization"), [
39
+ j.objectExpression([
40
+ j.objectProperty(
41
+ j.identifier("currentUser"),
42
+ j.objectExpression([
43
+ j.objectProperty(
44
+ j.identifier("id"),
45
+ j.memberExpression(j.identifier("user"), j.identifier("id"))
46
+ ),
47
+ j.objectProperty(
48
+ j.identifier("memberships"),
49
+ j.arrayExpression([])
50
+ )
51
+ ])
52
+ ),
53
+ j.objectProperty(
54
+ j.identifier("invitationToken"),
55
+ j.memberExpression(
56
+ j.identifier("userAttributes"),
57
+ j.identifier("invitationToken")
58
+ )
59
+ )
60
+ ])
61
+ ])
62
+ )
63
+ );
64
+ const returnUserStatement = j.returnStatement(j.identifier("user"));
65
+ if (j.BlockStatement.check(fn.body)) {
66
+ const returnIndex = fn.body.body.findIndex(
67
+ (statement) => j.ReturnStatement.check(statement)
68
+ );
69
+ const returnStatement = fn.body.body[returnIndex];
70
+ if (returnIndex === -1 || !j.ReturnStatement.check(returnStatement) || !returnStatement.argument) {
71
+ throw new Error("CEDAR_CODEMOD_ERR_SIGNUP_SHAPE_NOT_FOUND");
72
+ }
73
+ const createUserStatement = j.variableDeclaration("const", [
74
+ j.variableDeclarator(
75
+ j.identifier("user"),
76
+ j.awaitExpression(returnStatement.argument)
77
+ )
78
+ ]);
79
+ fn.body.body.splice(
80
+ returnIndex,
81
+ 1,
82
+ createUserStatement,
83
+ ensureCallStatement,
84
+ returnUserStatement
85
+ );
86
+ return;
87
+ }
88
+ if (j.Expression.check(fn.body)) {
89
+ const createUserStatement = j.variableDeclaration("const", [
90
+ j.variableDeclarator(
91
+ j.identifier("user"),
92
+ j.awaitExpression(
93
+ // ast-types' `Expression` checker type isn't assignable to
94
+ // `awaitExpression`'s narrower `ExpressionKind` union (a gap in
95
+ // ast-types' generated types); the `check()` call above already
96
+ // guarantees `fn.body` is a valid expression node.
97
+ fn.body
98
+ )
99
+ )
100
+ ]);
101
+ fn.body = j.blockStatement([
102
+ createUserStatement,
103
+ ensureCallStatement,
104
+ returnUserStatement
105
+ ]);
106
+ return;
107
+ }
108
+ throw new Error("CEDAR_CODEMOD_ERR_SIGNUP_SHAPE_NOT_FOUND");
109
+ }
110
+ function transform(fileInfo) {
111
+ const root = j.withParser("tsx")(fileInfo.source);
112
+ const signupOptionsDeclarators = root.find(j.VariableDeclarator, {
113
+ id: { name: "signupOptions" }
114
+ });
115
+ if (signupOptionsDeclarators.length === 0) {
116
+ throw new Error("CEDAR_CODEMOD_ERR_SIGNUP_SHAPE_NOT_FOUND");
117
+ }
118
+ let handlerFound = false;
119
+ signupOptionsDeclarators.forEach((path) => {
120
+ const init = path.node.init;
121
+ if (!j.ObjectExpression.check(init)) {
122
+ return;
123
+ }
124
+ const handlerProperty = init.properties.find(
125
+ (prop) => j.ObjectProperty.check(prop) && j.Identifier.check(prop.key) && prop.key.name === "handler"
126
+ );
127
+ if (!handlerProperty || !j.ObjectProperty.check(handlerProperty)) {
128
+ return;
129
+ }
130
+ const handlerFn = handlerProperty.value;
131
+ if (!j.ArrowFunctionExpression.check(handlerFn) && !j.FunctionExpression.check(handlerFn)) {
132
+ return;
133
+ }
134
+ useShorthandUserAttributes(handlerFn);
135
+ wrapCreateCall(handlerFn);
136
+ handlerFound = true;
137
+ });
138
+ if (!handlerFound) {
139
+ throw new Error("CEDAR_CODEMOD_ERR_SIGNUP_SHAPE_NOT_FOUND");
140
+ }
141
+ addInvitationTokenToUserAttributes(root);
142
+ const imports = root.find(j.ImportDeclaration);
143
+ imports.at(-1).insertAfter(
144
+ j.importDeclaration(
145
+ [j.importSpecifier(j.identifier("ensureDefaultOrganization"))],
146
+ j.literal("src/services/organizations/organizations")
147
+ )
148
+ );
149
+ return root.toSource();
150
+ }
151
+ export {
152
+ transform as default
153
+ };
@@ -0,0 +1,63 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ import { navigate, routes, useParams } from '@cedarjs/router'
4
+ import { Metadata, useMutation } from '@cedarjs/web'
5
+
6
+ import { useAuth } from 'src/auth'
7
+
8
+ const ACCEPT_INVITATION_MUTATION = gql`
9
+ mutation AcceptInvitationMutation($token: String!) {
10
+ acceptInvitation(token: $token) {
11
+ id
12
+ slug
13
+ name
14
+ }
15
+ }
16
+ `
17
+
18
+ /**
19
+ * Landing page for an invitation link (`/invite/{token}`). Outside
20
+ * `OrgScope`, since the visitor is not a member of the organization yet.
21
+ */
22
+ const InvitePage = () => {
23
+ const { token } = useParams()
24
+ const { isAuthenticated, reauthenticate } = useAuth()
25
+ const [error, setError] = useState<string | null>(null)
26
+
27
+ const [acceptInvitation] = useMutation(ACCEPT_INVITATION_MUTATION, {
28
+ onCompleted: async (data) => {
29
+ await reauthenticate()
30
+ // TODO: point this at your app's organization home route, e.g.
31
+ // navigate(routes.orgHome({ orgSlug: data.acceptInvitation.slug }))
32
+ navigate(`/org/${data.acceptInvitation.slug}`)
33
+ },
34
+ onError: (mutationError) => setError(mutationError.message),
35
+ })
36
+
37
+ useEffect(() => {
38
+ if (isAuthenticated && token) {
39
+ acceptInvitation({ variables: { token } })
40
+ }
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
+ }, [isAuthenticated, token])
43
+
44
+ if (!isAuthenticated) {
45
+ return (
46
+ <>
47
+ <Metadata title="Accept invitation" />
48
+ <p>Sign in or create an account to accept this invitation.</p>
49
+ <a href={`${routes.login()}?invitationToken=${token}`}>Log in</a>
50
+ <a href={`${routes.signup()}?invitationToken=${token}`}>Sign up</a>
51
+ </>
52
+ )
53
+ }
54
+
55
+ return (
56
+ <>
57
+ <Metadata title="Accept invitation" />
58
+ {error ? <p>{error}</p> : <p>Joining organization…</p>}
59
+ </>
60
+ )
61
+ }
62
+
63
+ export default InvitePage
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { OrgScope as TenancyOrgScope } from '@cedarjs/tenancy/web'
4
+
5
+ import { useAuth } from 'src/auth'
6
+
7
+ export interface OrgScopeProps {
8
+ /** Overrides the `orgSlug` route param. */
9
+ orgSlug?: string
10
+ children: ReactNode
11
+ }
12
+
13
+ /**
14
+ * Wraps organization routes with a per-organization Apollo client and a
15
+ * membership check, using this app's `useAuth`. Use with the router's
16
+ * `Set wrap`:
17
+ *
18
+ * <Set wrap={OrgScope}>
19
+ * <Route path="/org/{orgSlug}/projects" page={ProjectsPage} name="projects" />
20
+ * </Set>
21
+ *
22
+ * Routes under `OrgScope` must not carry `prerender`: `OrgScope`'s nested
23
+ * Apollo client shadows the per-route client the prerenderer injects above
24
+ * `App`, so a prerendered query under here would not go through it.
25
+ */
26
+ export const OrgScope = ({ orgSlug, children }: OrgScopeProps) => {
27
+ return (
28
+ <TenancyOrgScope
29
+ useAuth={useAuth}
30
+ orgSlug={orgSlug}
31
+ notAMember={
32
+ <div>
33
+ <p>You don&apos;t have access to this organization.</p>
34
+ </div>
35
+ }
36
+ >
37
+ {children}
38
+ </TenancyOrgScope>
39
+ )
40
+ }
@@ -0,0 +1,39 @@
1
+ import type { PrismaClient } from 'src/lib/db'
2
+ import { ensureDefaultOrganization } from 'src/services/organizations/organizations'
3
+
4
+ const BATCH_SIZE = 100
5
+
6
+ /**
7
+ * Existing users have no organization until this runs once: `setup tenancy`
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.
11
+ *
12
+ * Run with `__DATA_MIGRATE_UP_COMMAND__`, after
13
+ * `__PRISMA_MIGRATE_DEV_COMMAND__` has applied the
14
+ * `Organization`/`Membership` migration.
15
+ */
16
+ export default async ({ db }: { db: PrismaClient }) => {
17
+ let cursor: string | undefined
18
+ let hasMore = true
19
+
20
+ while (hasMore) {
21
+ const users = await db.user.findMany({
22
+ take: BATCH_SIZE,
23
+ orderBy: { id: 'asc' },
24
+ ...(cursor ? { skip: 1, cursor: { id: cursor } } : {}),
25
+ include: { memberships: { select: { id: true } } },
26
+ })
27
+
28
+ for (const user of users) {
29
+ if (user.memberships.length === 0) {
30
+ await ensureDefaultOrganization({
31
+ currentUser: { id: user.id, memberships: [] },
32
+ })
33
+ }
34
+ }
35
+
36
+ cursor = users[users.length - 1]?.id
37
+ hasMore = users.length === BATCH_SIZE
38
+ }
39
+ }