@cedarjs/cli 8.0.0-canary.3541 → 8.0.0-canary.3542
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.
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import j from "jscodeshift";
|
|
2
|
+
const TENANCY_WEB = "@cedarjs/tenancy/web";
|
|
3
|
+
function transform(fileInfo) {
|
|
4
|
+
const root = j.withParser("tsx")(fileInfo.source);
|
|
5
|
+
const routers = root.find(j.JSXOpeningElement, { name: { name: "Router" } });
|
|
6
|
+
if (routers.length !== 1) {
|
|
7
|
+
throw new Error("CEDAR_CODEMOD_ERR_ROUTER_NOT_FOUND");
|
|
8
|
+
}
|
|
9
|
+
const router = routers.get().node;
|
|
10
|
+
if (!j.JSXOpeningElement.check(router)) {
|
|
11
|
+
throw new Error("CEDAR_CODEMOD_ERR_ROUTER_NOT_FOUND");
|
|
12
|
+
}
|
|
13
|
+
const attributes = router.attributes ?? [];
|
|
14
|
+
const hasProp = attributes.some(
|
|
15
|
+
(attribute) => j.JSXAttribute.check(attribute) && attribute.name.name === "useOrgRoles"
|
|
16
|
+
);
|
|
17
|
+
const hasImport = root.find(j.ImportSpecifier, { local: { name: "useOrgRoles" } }).length > 0;
|
|
18
|
+
if (hasProp && hasImport) {
|
|
19
|
+
return fileInfo.source;
|
|
20
|
+
}
|
|
21
|
+
if (!hasProp) {
|
|
22
|
+
const useAuthIndex = attributes.findIndex(
|
|
23
|
+
(attribute) => j.JSXAttribute.check(attribute) && attribute.name.name === "useAuth"
|
|
24
|
+
);
|
|
25
|
+
const prop = j.jsxAttribute(
|
|
26
|
+
j.jsxIdentifier("useOrgRoles"),
|
|
27
|
+
j.jsxExpressionContainer(j.identifier("useOrgRoles"))
|
|
28
|
+
);
|
|
29
|
+
attributes.splice(
|
|
30
|
+
useAuthIndex === -1 ? attributes.length : useAuthIndex + 1,
|
|
31
|
+
0,
|
|
32
|
+
prop
|
|
33
|
+
);
|
|
34
|
+
router.attributes = attributes;
|
|
35
|
+
}
|
|
36
|
+
if (!hasImport) {
|
|
37
|
+
const specifier = j.importSpecifier(j.identifier("useOrgRoles"));
|
|
38
|
+
const tenancyImport = root.find(j.ImportDeclaration, { source: { value: TENANCY_WEB } }).nodes().find(
|
|
39
|
+
(declaration) => declaration.importKind !== "type" && declaration.specifiers?.every(
|
|
40
|
+
(existing) => j.ImportSpecifier.check(existing)
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
if (tenancyImport?.specifiers) {
|
|
44
|
+
tenancyImport.specifiers.push(specifier);
|
|
45
|
+
} else {
|
|
46
|
+
root.find(j.ImportDeclaration).at(-1).insertAfter(j.importDeclaration([specifier], j.literal(TENANCY_WEB)));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return root.toSource();
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
transform as default
|
|
53
|
+
};
|
|
@@ -27,17 +27,26 @@ export interface OrgScopeProps {
|
|
|
27
27
|
/**
|
|
28
28
|
* Wraps organization routes with a per-organization Apollo client and a
|
|
29
29
|
* membership check, using this app's `useAuth`. Use with the router's
|
|
30
|
-
* `
|
|
30
|
+
* `PrivateSet wrap`, so a user who isn't signed in is sent to log in:
|
|
31
31
|
*
|
|
32
|
-
* <
|
|
32
|
+
* <PrivateSet wrap={OrgScope} unauthenticated="login">
|
|
33
33
|
* <Route path="/org/{orgSlug}/projects" page={ProjectsPage} name="projects" />
|
|
34
|
-
* </
|
|
34
|
+
* </PrivateSet>
|
|
35
35
|
*
|
|
36
36
|
* Under it, `useAuth().hasPermission` and `useCurrentOrg().hasOrgRole`
|
|
37
37
|
* check the user's membership in the organization, and
|
|
38
38
|
* `useAuth().hasUserRole` checks `currentUser.roles`. For pages only some
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* roles in the organization may open, give the `PrivateSet` `orgRoles`. The
|
|
40
|
+
* router checks the role in the route's organization with the
|
|
41
|
+
* `useOrgRoles` passed to it, `<Router useAuth={useAuth}
|
|
42
|
+
* useOrgRoles={useOrgRoles}>`:
|
|
43
|
+
*
|
|
44
|
+
* <PrivateSet wrap={[OrgScope, OrgLayout]} unauthenticated="login" unauthorized="projects" orgRoles="owner">
|
|
45
|
+
* <Route path="/org/{orgSlug}/settings" page={SettingsPage} name="settings" />
|
|
46
|
+
* </PrivateSet>
|
|
47
|
+
*
|
|
48
|
+
* For pages only members with a permission may open, wrap them in a
|
|
49
|
+
* component that passes `permission`:
|
|
41
50
|
*
|
|
42
51
|
* const MemberManagers = ({ children }: { children: ReactNode }) => (
|
|
43
52
|
* <OrgScope permission="members.manage" unauthorized="projects">
|
|
@@ -454,6 +454,28 @@ ${SIGNUP_SNIPPET}`;
|
|
|
454
454
|
);
|
|
455
455
|
}
|
|
456
456
|
},
|
|
457
|
+
{
|
|
458
|
+
title: "Passing useOrgRoles to the Router in web/src/Routes...",
|
|
459
|
+
task: async (ctx, task) => {
|
|
460
|
+
const routesPath = paths.web.routes;
|
|
461
|
+
if (!routesPath || !fs.existsSync(routesPath)) {
|
|
462
|
+
ctx.routerNeedsUseOrgRoles = true;
|
|
463
|
+
task.skip("No web/src/Routes file found; skipping.");
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const source = fs.readFileSync(routesPath, "utf-8");
|
|
467
|
+
const transformResult = await runTransform({
|
|
468
|
+
transformPath: path.join(import.meta.dirname, "routerCodemod.js"),
|
|
469
|
+
targetPaths: [routesPath]
|
|
470
|
+
});
|
|
471
|
+
if (transformResult.error) {
|
|
472
|
+
ctx.routerNeedsUseOrgRoles = true;
|
|
473
|
+
task.output = "Could not find the app's <Router> in web/src/Routes, so left it as it is. See the next steps for what to add.";
|
|
474
|
+
} else if (fs.readFileSync(routesPath, "utf-8") !== source) {
|
|
475
|
+
writtenFiles.push(routesPath);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
},
|
|
457
479
|
{
|
|
458
480
|
title: "Formatting and linting changed files...",
|
|
459
481
|
task: async (_ctx, task) => {
|
|
@@ -521,16 +543,24 @@ ${SIGNUP_SNIPPET}`;
|
|
|
521
543
|
to ${c.highlight("@requireOrgAuth")}. For each one that stays global, add it
|
|
522
544
|
to the ${c.highlight("allExcept")} list in api/src/lib/db instead.
|
|
523
545
|
3. Wrap your organization routes with the generated OrgScope in
|
|
524
|
-
web/src/Routes.tsx
|
|
546
|
+
web/src/Routes.tsx, in a PrivateSet, so a user who isn't signed
|
|
547
|
+
in is sent to log in:
|
|
525
548
|
|
|
526
|
-
<
|
|
549
|
+
<PrivateSet wrap={OrgScope} unauthenticated="login">
|
|
527
550
|
<Route path="/org/{orgSlug}/projects" page={ProjectsPage} name="projects" />
|
|
528
|
-
</
|
|
551
|
+
</PrivateSet>
|
|
529
552
|
|
|
530
553
|
and add the invitation route outside it:
|
|
531
554
|
|
|
532
555
|
<Route path="/invite/{token}" page={InvitePage} name="invite" />
|
|
556
|
+
${ctx.routerNeedsUseOrgRoles ? `
|
|
557
|
+
Your ${c.highlight("<Router>")} couldn't be found, so pass it ${c.highlight("useOrgRoles")} by
|
|
558
|
+
hand, for ${c.highlight("<PrivateSet orgRoles>")} below:
|
|
559
|
+
|
|
560
|
+
import { useOrgRoles } from '@cedarjs/tenancy/web'
|
|
533
561
|
|
|
562
|
+
<Router useAuth={useAuth} useOrgRoles={useOrgRoles}>
|
|
563
|
+
` : ""}
|
|
534
564
|
4. Wrap any plain api/src/functions/* handler that touches
|
|
535
565
|
tenant-owned models with ${c.highlight("withTenancy")} from ${c.highlight(
|
|
536
566
|
"@cedarjs/tenancy"
|
|
@@ -549,12 +579,19 @@ ${SIGNUP_SNIPPET}`;
|
|
|
549
579
|
it and your @requireAuth fields were left as they are: move
|
|
550
580
|
its checks to requireUserAuth or requireOrgAuth, switch the
|
|
551
581
|
fields over, then delete it.` : ""}
|
|
552
|
-
|
|
582
|
+
7. Every role check names its kind now. ${c.highlight("hasRole")} is
|
|
553
583
|
${c.highlight("hasUserRole")} on both sides (your existing uses were renamed),
|
|
554
584
|
checking ${c.highlight("currentUser.roles")}; ${c.highlight("hasOrgRole")} checks the role in
|
|
555
585
|
the organization. ${c.highlight("<PrivateSet roles>")} isn't available with
|
|
556
|
-
tenancy: guard routes
|
|
557
|
-
${c.highlight("
|
|
586
|
+
tenancy: guard routes with ${c.highlight("<PrivateSet userRoles>")} for
|
|
587
|
+
${c.highlight("currentUser.roles")}, and with ${c.highlight("<PrivateSet orgRoles>")} for the
|
|
588
|
+
role in the route's organization, which the router checks with
|
|
589
|
+
the ${c.highlight("useOrgRoles")} passed to it:
|
|
590
|
+
|
|
591
|
+
<Router useAuth={useAuth} useOrgRoles={useOrgRoles}>
|
|
592
|
+
<PrivateSet wrap={[OrgScope, OrgLayout]} unauthenticated="login" unauthorized="projects" orgRoles="owner">
|
|
593
|
+
<Route path="/org/{orgSlug}/settings" page={SettingsPage} name="settings" />
|
|
594
|
+
</PrivateSet>
|
|
558
595
|
|
|
559
596
|
${keptFiles.length > 0 ? `
|
|
560
597
|
Kept these existing files rather than overwriting them (run with
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/cli",
|
|
3
|
-
"version": "8.0.0-canary.
|
|
3
|
+
"version": "8.0.0-canary.3542",
|
|
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.10",
|
|
38
38
|
"@babel/types": "7.29.11",
|
|
39
|
-
"@cedarjs/api-server": "8.0.0-canary.
|
|
40
|
-
"@cedarjs/babel-config": "8.0.0-canary.
|
|
41
|
-
"@cedarjs/cli-helpers": "8.0.0-canary.
|
|
42
|
-
"@cedarjs/internal": "8.0.0-canary.
|
|
43
|
-
"@cedarjs/prerender": "8.0.0-canary.
|
|
44
|
-
"@cedarjs/project-config": "8.0.0-canary.
|
|
45
|
-
"@cedarjs/structure": "8.0.0-canary.
|
|
46
|
-
"@cedarjs/telemetry": "8.0.0-canary.
|
|
47
|
-
"@cedarjs/utils": "8.0.0-canary.
|
|
48
|
-
"@cedarjs/vite": "8.0.0-canary.
|
|
49
|
-
"@cedarjs/web-server": "8.0.0-canary.
|
|
39
|
+
"@cedarjs/api-server": "8.0.0-canary.3542",
|
|
40
|
+
"@cedarjs/babel-config": "8.0.0-canary.3542",
|
|
41
|
+
"@cedarjs/cli-helpers": "8.0.0-canary.3542",
|
|
42
|
+
"@cedarjs/internal": "8.0.0-canary.3542",
|
|
43
|
+
"@cedarjs/prerender": "8.0.0-canary.3542",
|
|
44
|
+
"@cedarjs/project-config": "8.0.0-canary.3542",
|
|
45
|
+
"@cedarjs/structure": "8.0.0-canary.3542",
|
|
46
|
+
"@cedarjs/telemetry": "8.0.0-canary.3542",
|
|
47
|
+
"@cedarjs/utils": "8.0.0-canary.3542",
|
|
48
|
+
"@cedarjs/vite": "8.0.0-canary.3542",
|
|
49
|
+
"@cedarjs/web-server": "8.0.0-canary.3542",
|
|
50
50
|
"@listr2/prompt-adapter-enquirer": "4.4.3",
|
|
51
51
|
"@opentelemetry/api": "1.9.1",
|
|
52
52
|
"@opentelemetry/core": "2.12.0",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"yargs": "18.2.0"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
96
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3542",
|
|
97
97
|
"@prisma/dmmf": "7.10.1",
|
|
98
98
|
"@types/archiver": "^7.0.0",
|
|
99
99
|
"memfs": "4.80.0",
|