minutework 0.1.33 → 0.1.35
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/EXTERNAL_ALPHA.md +8 -6
- package/README.md +1 -1
- package/assets/claude-local/CLAUDE.md.template +12 -8
- package/assets/claude-local/skills/app-pack-authoring/SKILL.md +10 -0
- package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +9 -0
- package/assets/templates/next-tenant-app/.env.example +2 -8
- package/assets/templates/next-tenant-app/README.md +18 -0
- package/assets/templates/next-tenant-app/src/app/app/demo/page.tsx +1 -7
- package/assets/templates/next-tenant-app/src/app/app/layout.tsx +9 -1
- package/assets/templates/next-tenant-app/src/features/shell/components/authenticated-app-layout-shell.tsx +181 -0
- package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +3 -113
- package/assets/templates/next-tenant-app/src/mw/mock.test.ts +2 -1
- package/assets/templates/next-tenant-app/src/mw/mock.ts +3 -1
- package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +120 -1
- package/assets/templates/next-tenant-app/tools/template/with-public-site-fixture.mjs +28 -114
- package/assets/templates/next-tenant-app/vitest.config.ts +2 -6
- package/dist/workspace-bootstrap.js +2 -5
- package/dist/workspace-bootstrap.js.map +1 -1
- package/package.json +1 -1
package/EXTERNAL_ALPHA.md
CHANGED
|
@@ -120,14 +120,16 @@ If you run `minutework init` **without** `--starter` in an interactive terminal,
|
|
|
120
120
|
The generated `tenant-app/.env.example` defaults to:
|
|
121
121
|
|
|
122
122
|
```dotenv
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
NEXT_PUBLIC_MW_APP_ID=tenant.app
|
|
124
|
+
MW_TEMPLATE_APP_NAME=Tenant App
|
|
125
|
+
MW_PUBLIC_BASE_URL=
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
`
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
`tenant-app` browser auth and manifest calls use same-origin `/_mw` routes
|
|
129
|
+
through `@minutework/web-auth`. Do not add platform content tokens or
|
|
130
|
+
public-site property variables to the SDK-based tenant app; public content
|
|
131
|
+
should come from hosted published releases/snapshots or gateway-approved
|
|
132
|
+
public-content routes over the MinuteWork substrate.
|
|
131
133
|
|
|
132
134
|
If the workspace cannot compile hosted preview release metadata for the linked property, deploy fails closed.
|
|
133
135
|
|
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ Combined **tenant-app + sidecar** workspaces keep sidecar setup explicit. Root `
|
|
|
122
122
|
|
|
123
123
|
For direct third-party web hosts such as Vercel, deploy `tenant-app` only. In Vercel, point the project Root Directory at `tenant-app`; the optional sidecar remains a separate Poetry-managed surface.
|
|
124
124
|
|
|
125
|
-
`link` provisions or resolves the default published-site property for the active tenant and stores that property key in repo-local state. The
|
|
125
|
+
`link` provisions or resolves the default published-site property for the active tenant and stores that property key in repo-local state. The SDK-based `tenant-app/.env.example` contains only the app metadata contract used by the template: `NEXT_PUBLIC_MW_APP_ID`, `MW_TEMPLATE_APP_NAME`, and optional `MW_PUBLIC_BASE_URL`. Browser auth and manifest calls use same-origin `/_mw` routes through `@minutework/web-auth`; do not add platform content tokens or public-site property vars to the tenant app.
|
|
126
126
|
|
|
127
127
|
`deploy --preview` always revalidates and recompiles before submit, prints a local-vs-remote diff, requires confirmation unless `--yes` is passed, polls typed receipts until a terminal state, and persists the last known preview deploy state under `.minutework/deploy/preview/status.json`. This alpha is preview-first; live public delivery remains follow-on.
|
|
128
128
|
|
|
@@ -6,10 +6,13 @@ smallest surface that fits the request.
|
|
|
6
6
|
|
|
7
7
|
`tenant-app` is the combined web starter: public site routes at the root plus a
|
|
8
8
|
private authenticated workspace under `/app`.
|
|
9
|
+
It uses `@minutework/web-auth` through a thin `src/mw/` substrate and
|
|
10
|
+
same-origin `/_mw` routes; do not add per-app auth/gateway BFF routes or
|
|
11
|
+
browser-stored tokens.
|
|
9
12
|
|
|
10
13
|
`mobile` is the standalone Expo/React Native client starter. It talks directly
|
|
11
14
|
to the platform with native device-flow auth and bearer tokens; it is not a
|
|
12
|
-
`tenant-app`
|
|
15
|
+
`tenant-app` web SDK cookie client and not a `sidecar`. It ships through the
|
|
13
16
|
developer's own EAS/App Store/Play Store pipeline, not `minutework deploy`.
|
|
14
17
|
|
|
15
18
|
## Refresh Managed Guidance
|
|
@@ -189,13 +192,14 @@ a concrete backend responsibility such as:
|
|
|
189
192
|
- `mw.core.site` already includes `config`, `page`, `nav`, `form`, and
|
|
190
193
|
`submission` schemas before Builder starts authoring.
|
|
191
194
|
- Author public content against runtime/CMS records and published-web flows, not
|
|
192
|
-
against a fixed in-repo marketing template
|
|
193
|
-
|
|
194
|
-
-
|
|
195
|
-
`
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
against a fixed in-repo marketing template or a server-only content-token
|
|
196
|
+
adapter in `tenant-app`.
|
|
197
|
+
- The SDK-based `tenant-app` public metadata env contract is
|
|
198
|
+
`NEXT_PUBLIC_MW_APP_ID`, `MW_TEMPLATE_APP_NAME`, and optional
|
|
199
|
+
`MW_PUBLIC_BASE_URL`; do not add legacy `MW_CONTENT_API_TOKEN`,
|
|
200
|
+
`MW_PUBLIC_CONTENT_SOURCE`, `MW_PUBLIC_SITE_PROPERTY_KEY`,
|
|
201
|
+
`MW_PUBLIC_SITE_ENV`, or `MW_STATIC_PUBLIC_CONTENT_PATH` variables unless a
|
|
202
|
+
compatibility task explicitly targets that old adapter.
|
|
199
203
|
- Anonymous live delivery should prefer published snapshots instead of direct
|
|
200
204
|
runtime reads on every request.
|
|
201
205
|
- `runtime_local_sidecar` is an opt-in public-site serving exception, not the
|
|
@@ -21,6 +21,16 @@ An `app pack` is the shipped product unit.
|
|
|
21
21
|
per-app `src/app/api/auth/*`, `src/app/api/gateway/*`, platform-session BFF
|
|
22
22
|
layers, platform credential login screens, operator-console links, or
|
|
23
23
|
runtime-command demos.
|
|
24
|
+
- If a generated workspace reports `@minutework/web-auth` as missing, treat it
|
|
25
|
+
as dependency installation, package publishing, or package sync work. Do not
|
|
26
|
+
hand-roll auth/data routes or browser tokens as a workaround.
|
|
27
|
+
- The template may render a local `/login` page that calls SDK hooks. The SDK
|
|
28
|
+
also exposes hosted UI helpers for same-origin `/_mw/login`,
|
|
29
|
+
`/_mw/signup`, and `/_mw/verify-email`; both choices remain SDK-only.
|
|
30
|
+
- Client-side `/app` session checks are UX gating only. Real authorization is
|
|
31
|
+
enforced server-side by platform `/_mw` routes and runtime dispatch checks:
|
|
32
|
+
active customer membership, email verification, app publication, and
|
|
33
|
+
`webCustomerExposed` / `web_customer_exposed` manifest declarations.
|
|
24
34
|
- Browser calls from `tenant-app` should use `mw.query(...)` and
|
|
25
35
|
`mw.action(..., { idempotencyKey })`; actions require a non-empty
|
|
26
36
|
idempotency key. The runtime surface must be declared on the manifest with
|
|
@@ -26,6 +26,15 @@ the monorepo or a live tenant runtime.
|
|
|
26
26
|
`src/mw/` substrate and `@minutework/web-auth`. Product UI may call that
|
|
27
27
|
substrate, but it must not recreate platform-session BFF routes, platform
|
|
28
28
|
credential login, or browser-exposed platform/runtime tokens.
|
|
29
|
+
- If `@minutework/web-auth` is not installed or resolvable in a generated
|
|
30
|
+
workspace, fix dependency installation, package publishing, or package sync.
|
|
31
|
+
Do not replace the SDK with local auth/gateway routes.
|
|
32
|
+
- A local `/login` page may call SDK hooks, while SDK hosted UI helpers point
|
|
33
|
+
to same-origin `/_mw/login`, `/_mw/signup`, and `/_mw/verify-email`; both
|
|
34
|
+
are valid only when they stay on the SDK/`/_mw` contract.
|
|
35
|
+
- Client-side app guards are UX only. Platform `/_mw` routes and runtime
|
|
36
|
+
dispatch enforce active customer membership, email verification, app
|
|
37
|
+
publication, and manifest exposure server-side.
|
|
29
38
|
- `tenant-app` runtime data access goes through `mw.query(...)` and
|
|
30
39
|
`mw.action(..., { idempotencyKey })` against manifest-declared
|
|
31
40
|
`webCustomerExposed` / `web_customer_exposed` customer surfaces.
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
MW_CONTENT_API_TOKEN=
|
|
4
|
-
MW_TEMPLATE_APP_NAME=MinuteWork Combined Starter
|
|
1
|
+
NEXT_PUBLIC_MW_APP_ID=tenant.app
|
|
2
|
+
MW_TEMPLATE_APP_NAME=Tenant App
|
|
5
3
|
MW_PUBLIC_BASE_URL=
|
|
6
|
-
MW_PUBLIC_SITE_PROPERTY_KEY=
|
|
7
|
-
MW_PUBLIC_SITE_ENV=preview
|
|
8
|
-
MW_STATIC_PUBLIC_CONTENT_PATH=content/public-site.json
|
|
9
|
-
MW_ENABLE_RUNTIME_COMMAND_EXAMPLE=false
|
|
@@ -22,6 +22,20 @@ This template uses the `tenant_web_auth_sdk` profile:
|
|
|
22
22
|
Only `src/mw/` is MinuteWork substrate. Keep product UI and product logic in
|
|
23
23
|
`src/app`, `src/features`, and developer-owned modules.
|
|
24
24
|
|
|
25
|
+
The local `/login` route is product UI that calls SDK hooks. The SDK also
|
|
26
|
+
provides hosted UI helpers for same-origin `/_mw/login`, `/_mw/signup`, and
|
|
27
|
+
`/_mw/verify-email` URLs; both paths stay on the same SDK contract.
|
|
28
|
+
|
|
29
|
+
Client-side `/app` session checks are only UX gating. Authorization is enforced
|
|
30
|
+
by the platform `/_mw` routes and runtime dispatch: active customer membership,
|
|
31
|
+
email verification, app publication, and manifest `web_customer_exposed`
|
|
32
|
+
declarations are checked server-side.
|
|
33
|
+
|
|
34
|
+
If a generated workspace reports `@minutework/web-auth` as missing, treat that
|
|
35
|
+
as a dependency installation or package publishing/sync issue. Do not recreate
|
|
36
|
+
platform BFF routes, browser bearer tokens, or platform credential login as a
|
|
37
|
+
workaround.
|
|
38
|
+
|
|
25
39
|
## Default route shape
|
|
26
40
|
|
|
27
41
|
- public routes at `/`, `/pricing`, `/docs`, and `/blog`
|
|
@@ -29,6 +43,10 @@ Only `src/mw/` is MinuteWork substrate. Keep product UI and product logic in
|
|
|
29
43
|
- authenticated workspace at `/app`
|
|
30
44
|
- SDK manifest demo at `/app/demo`
|
|
31
45
|
|
|
46
|
+
All `/app/*` routes are wrapped by `src/app/app/layout.tsx`, which renders the
|
|
47
|
+
shared authenticated layout guard. Individual `/app` pages should assume an
|
|
48
|
+
authenticated tenant-customer session instead of reimplementing auth checks.
|
|
49
|
+
|
|
32
50
|
The template intentionally does not include `src/lib/platform/*`,
|
|
33
51
|
`src/app/api/auth/*`, `src/app/api/gateway/*`, an operator-console link, a
|
|
34
52
|
runtime-command demo, or a server-only public-content token adapter.
|
|
@@ -5,11 +5,5 @@ export const metadata = {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
export default function DemoPage() {
|
|
8
|
-
return
|
|
9
|
-
<main className="min-h-screen bg-background text-foreground">
|
|
10
|
-
<div className="mx-auto flex min-h-screen max-w-5xl flex-col gap-6 px-6 py-8">
|
|
11
|
-
<ManifestDemo />
|
|
12
|
-
</div>
|
|
13
|
-
</main>
|
|
14
|
-
);
|
|
8
|
+
return <ManifestDemo />;
|
|
15
9
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
|
|
4
|
+
import { AuthenticatedAppLayoutShell } from "@/features/shell/components/authenticated-app-layout-shell";
|
|
5
|
+
|
|
4
6
|
export const metadata: Metadata = {
|
|
5
7
|
robots: {
|
|
6
8
|
index: false,
|
|
@@ -13,5 +15,11 @@ export default function AuthenticatedAppLayout({
|
|
|
13
15
|
}: {
|
|
14
16
|
children: ReactNode;
|
|
15
17
|
}) {
|
|
16
|
-
return
|
|
18
|
+
return (
|
|
19
|
+
<AuthenticatedAppLayoutShell
|
|
20
|
+
appName={process.env.MW_TEMPLATE_APP_NAME || "Tenant App"}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</AuthenticatedAppLayoutShell>
|
|
24
|
+
);
|
|
17
25
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { startTransition } from "react";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { useRouter } from "next/navigation";
|
|
7
|
+
import { LayoutDashboard, LogOut, PlaySquare } from "lucide-react";
|
|
8
|
+
import type {
|
|
9
|
+
TenantCustomerMembership,
|
|
10
|
+
TenantCustomerSession,
|
|
11
|
+
} from "@minutework/web-auth";
|
|
12
|
+
import {
|
|
13
|
+
useMinuteWorkAuth,
|
|
14
|
+
useMinuteWorkSession,
|
|
15
|
+
} from "@minutework/web-auth/react";
|
|
16
|
+
|
|
17
|
+
import { PanelFrame } from "@/design-system/patterns/panel-frame";
|
|
18
|
+
import { ThemeModeToggle } from "@/design-system/patterns/theme-mode-toggle";
|
|
19
|
+
import { Button } from "@/design-system/primitives/button";
|
|
20
|
+
import { appRoutes } from "@/lib/app-routes";
|
|
21
|
+
|
|
22
|
+
type AuthenticatedTenantCustomerSession = TenantCustomerSession & {
|
|
23
|
+
customer_membership: TenantCustomerMembership;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function useAuthenticatedTenantCustomerSession(): AuthenticatedTenantCustomerSession {
|
|
27
|
+
const { session } = useMinuteWorkSession();
|
|
28
|
+
|
|
29
|
+
if (!session?.customer_membership) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"useAuthenticatedTenantCustomerSession must be rendered below AuthenticatedAppLayoutShell.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return session as AuthenticatedTenantCustomerSession;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function AuthenticatedAppLayoutShell({
|
|
39
|
+
appName,
|
|
40
|
+
children,
|
|
41
|
+
}: {
|
|
42
|
+
appName: string;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}) {
|
|
45
|
+
const router = useRouter();
|
|
46
|
+
const { logout } = useMinuteWorkAuth();
|
|
47
|
+
const { session, loading, authenticated, emailVerificationRequired } =
|
|
48
|
+
useMinuteWorkSession();
|
|
49
|
+
|
|
50
|
+
function redirectToLogin() {
|
|
51
|
+
startTransition(() => {
|
|
52
|
+
router.replace(appRoutes.login);
|
|
53
|
+
router.refresh();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function handleLogout() {
|
|
58
|
+
await logout().catch(() => undefined);
|
|
59
|
+
redirectToLogin();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (loading) {
|
|
63
|
+
return (
|
|
64
|
+
<main className="min-h-screen bg-background text-foreground">
|
|
65
|
+
<div className="mx-auto flex min-h-screen max-w-5xl items-center px-6 py-10">
|
|
66
|
+
<PanelFrame tone="floating" radius="xl" padding="lg" className="w-full">
|
|
67
|
+
<p className="text-sm text-muted-foreground">Loading session</p>
|
|
68
|
+
</PanelFrame>
|
|
69
|
+
</div>
|
|
70
|
+
</main>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
emailVerificationRequired ||
|
|
76
|
+
session?.email_verification_required ||
|
|
77
|
+
session?.customer_membership?.status === "pending_verification"
|
|
78
|
+
) {
|
|
79
|
+
return (
|
|
80
|
+
<main className="min-h-screen bg-background text-foreground">
|
|
81
|
+
<div className="mx-auto flex min-h-screen max-w-5xl items-center px-6 py-10">
|
|
82
|
+
<PanelFrame
|
|
83
|
+
tone="floating"
|
|
84
|
+
radius="xl"
|
|
85
|
+
padding="lg"
|
|
86
|
+
className="w-full space-y-4"
|
|
87
|
+
>
|
|
88
|
+
<div className="space-y-2">
|
|
89
|
+
<h1 className="text-3xl font-semibold tracking-tight">
|
|
90
|
+
Email verification required
|
|
91
|
+
</h1>
|
|
92
|
+
<p className="text-sm leading-7 text-muted-foreground">
|
|
93
|
+
Finish verification from your email before opening the workspace.
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
<Button onClick={redirectToLogin} className="w-fit">
|
|
97
|
+
Open login
|
|
98
|
+
</Button>
|
|
99
|
+
</PanelFrame>
|
|
100
|
+
</div>
|
|
101
|
+
</main>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!authenticated || !session?.customer_membership) {
|
|
106
|
+
return (
|
|
107
|
+
<main className="min-h-screen bg-background text-foreground">
|
|
108
|
+
<div className="mx-auto flex min-h-screen max-w-5xl items-center px-6 py-10">
|
|
109
|
+
<PanelFrame
|
|
110
|
+
tone="floating"
|
|
111
|
+
radius="xl"
|
|
112
|
+
padding="lg"
|
|
113
|
+
className="w-full space-y-4"
|
|
114
|
+
>
|
|
115
|
+
<div className="space-y-2">
|
|
116
|
+
<h1 className="text-3xl font-semibold tracking-tight">
|
|
117
|
+
Log in to continue
|
|
118
|
+
</h1>
|
|
119
|
+
<p className="text-sm leading-7 text-muted-foreground">
|
|
120
|
+
This area is available to verified customers.
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
<Button onClick={redirectToLogin} className="w-fit">
|
|
124
|
+
Open login
|
|
125
|
+
</Button>
|
|
126
|
+
</PanelFrame>
|
|
127
|
+
</div>
|
|
128
|
+
</main>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<main className="min-h-screen bg-background text-foreground">
|
|
134
|
+
<div className="mx-auto flex min-h-screen max-w-7xl flex-col gap-6 px-6 py-8">
|
|
135
|
+
<header className="flex flex-col gap-6 xl:flex-row xl:items-start xl:justify-between">
|
|
136
|
+
<div className="space-y-2">
|
|
137
|
+
<p className="text-sm font-semibold uppercase tracking-widest text-muted-foreground">
|
|
138
|
+
{session.tenant.name}
|
|
139
|
+
</p>
|
|
140
|
+
<h1 className="max-w-3xl text-4xl font-semibold tracking-tight text-balance sm:text-5xl">
|
|
141
|
+
{appName}
|
|
142
|
+
</h1>
|
|
143
|
+
<p className="max-w-3xl text-base leading-7 text-muted-foreground sm:text-lg">
|
|
144
|
+
Signed in as {session.user?.email || session.user?.username}.
|
|
145
|
+
</p>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start">
|
|
149
|
+
<ThemeModeToggle className="w-full sm:w-72" />
|
|
150
|
+
<Button
|
|
151
|
+
type="button"
|
|
152
|
+
variant="outline"
|
|
153
|
+
className="gap-2"
|
|
154
|
+
onClick={handleLogout}
|
|
155
|
+
>
|
|
156
|
+
<LogOut className="size-4" />
|
|
157
|
+
Log out
|
|
158
|
+
</Button>
|
|
159
|
+
</div>
|
|
160
|
+
</header>
|
|
161
|
+
|
|
162
|
+
<nav className="flex flex-wrap gap-2">
|
|
163
|
+
<Button asChild variant="default">
|
|
164
|
+
<Link href={appRoutes.appHome}>
|
|
165
|
+
<LayoutDashboard className="size-4" />
|
|
166
|
+
Dashboard
|
|
167
|
+
</Link>
|
|
168
|
+
</Button>
|
|
169
|
+
<Button asChild variant="outline">
|
|
170
|
+
<Link href={appRoutes.demo}>
|
|
171
|
+
<PlaySquare className="size-4" />
|
|
172
|
+
Demo
|
|
173
|
+
</Link>
|
|
174
|
+
</Button>
|
|
175
|
+
</nav>
|
|
176
|
+
|
|
177
|
+
{children}
|
|
178
|
+
</div>
|
|
179
|
+
</main>
|
|
180
|
+
);
|
|
181
|
+
}
|
package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx
CHANGED
|
@@ -1,120 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { startTransition } from "react";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import { useRouter } from "next/navigation";
|
|
6
|
-
import { LayoutDashboard, LogOut, PlaySquare } from "lucide-react";
|
|
7
|
-
|
|
8
|
-
import { PanelFrame } from "@/design-system/patterns/panel-frame";
|
|
9
|
-
import { ThemeModeToggle } from "@/design-system/patterns/theme-mode-toggle";
|
|
10
|
-
import { Button } from "@/design-system/primitives/button";
|
|
11
3
|
import { TenantDashboard } from "@/features/dashboard/components/tenant-dashboard";
|
|
12
|
-
import {
|
|
13
|
-
import { useMinuteWorkAuth, useMinuteWorkSession } from "@minutework/web-auth/react";
|
|
4
|
+
import { useAuthenticatedTenantCustomerSession } from "@/features/shell/components/authenticated-app-layout-shell";
|
|
14
5
|
|
|
15
6
|
export function PrivateAppShell({ appName }: { appName: string }) {
|
|
16
|
-
const
|
|
17
|
-
const { logout } = useMinuteWorkAuth();
|
|
18
|
-
const { session, loading, authenticated, emailVerificationRequired } =
|
|
19
|
-
useMinuteWorkSession();
|
|
20
|
-
|
|
21
|
-
function redirectToLogin() {
|
|
22
|
-
startTransition(() => {
|
|
23
|
-
router.replace(appRoutes.login);
|
|
24
|
-
router.refresh();
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function handleLogout() {
|
|
29
|
-
await logout().catch(() => undefined);
|
|
30
|
-
redirectToLogin();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (loading) {
|
|
34
|
-
return (
|
|
35
|
-
<main className="min-h-screen bg-background text-foreground">
|
|
36
|
-
<div className="mx-auto flex min-h-screen max-w-5xl items-center px-6 py-10">
|
|
37
|
-
<PanelFrame tone="floating" radius="xl" padding="lg" className="w-full">
|
|
38
|
-
<p className="text-sm text-muted-foreground">Loading session</p>
|
|
39
|
-
</PanelFrame>
|
|
40
|
-
</div>
|
|
41
|
-
</main>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!authenticated || !session?.customer_membership) {
|
|
46
|
-
return (
|
|
47
|
-
<main className="min-h-screen bg-background text-foreground">
|
|
48
|
-
<div className="mx-auto flex min-h-screen max-w-5xl items-center px-6 py-10">
|
|
49
|
-
<PanelFrame tone="floating" radius="xl" padding="lg" className="w-full space-y-4">
|
|
50
|
-
<div className="space-y-2">
|
|
51
|
-
<h1 className="text-3xl font-semibold tracking-tight">
|
|
52
|
-
{emailVerificationRequired
|
|
53
|
-
? "Email verification required"
|
|
54
|
-
: "Log in to continue"}
|
|
55
|
-
</h1>
|
|
56
|
-
<p className="text-sm leading-7 text-muted-foreground">
|
|
57
|
-
{emailVerificationRequired
|
|
58
|
-
? "Finish verification from your email before opening the workspace."
|
|
59
|
-
: "This area is available to verified customers."}
|
|
60
|
-
</p>
|
|
61
|
-
</div>
|
|
62
|
-
<Button onClick={redirectToLogin} className="w-fit">
|
|
63
|
-
Open login
|
|
64
|
-
</Button>
|
|
65
|
-
</PanelFrame>
|
|
66
|
-
</div>
|
|
67
|
-
</main>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<main className="min-h-screen bg-background text-foreground">
|
|
73
|
-
<div className="mx-auto flex min-h-screen max-w-7xl flex-col gap-6 px-6 py-8">
|
|
74
|
-
<header className="flex flex-col gap-6 xl:flex-row xl:items-start xl:justify-between">
|
|
75
|
-
<div className="space-y-2">
|
|
76
|
-
<p className="text-sm font-semibold uppercase tracking-widest text-muted-foreground">
|
|
77
|
-
{session.tenant.name}
|
|
78
|
-
</p>
|
|
79
|
-
<h1 className="max-w-3xl text-4xl font-semibold tracking-tight text-balance sm:text-5xl">
|
|
80
|
-
{appName}
|
|
81
|
-
</h1>
|
|
82
|
-
<p className="max-w-3xl text-base leading-7 text-muted-foreground sm:text-lg">
|
|
83
|
-
Signed in as {session.user?.email || session.user?.username}.
|
|
84
|
-
</p>
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
<div className="flex flex-col gap-3 sm:flex-row sm:items-start">
|
|
88
|
-
<ThemeModeToggle className="w-full sm:w-72" />
|
|
89
|
-
<Button
|
|
90
|
-
type="button"
|
|
91
|
-
variant="outline"
|
|
92
|
-
className="gap-2"
|
|
93
|
-
onClick={handleLogout}
|
|
94
|
-
>
|
|
95
|
-
<LogOut className="size-4" />
|
|
96
|
-
Log out
|
|
97
|
-
</Button>
|
|
98
|
-
</div>
|
|
99
|
-
</header>
|
|
100
|
-
|
|
101
|
-
<nav className="flex flex-wrap gap-2">
|
|
102
|
-
<Button asChild variant="default">
|
|
103
|
-
<Link href={appRoutes.appHome}>
|
|
104
|
-
<LayoutDashboard className="size-4" />
|
|
105
|
-
Dashboard
|
|
106
|
-
</Link>
|
|
107
|
-
</Button>
|
|
108
|
-
<Button asChild variant="outline">
|
|
109
|
-
<Link href={appRoutes.demo}>
|
|
110
|
-
<PlaySquare className="size-4" />
|
|
111
|
-
Demo
|
|
112
|
-
</Link>
|
|
113
|
-
</Button>
|
|
114
|
-
</nav>
|
|
7
|
+
const session = useAuthenticatedTenantCustomerSession();
|
|
115
8
|
|
|
116
|
-
|
|
117
|
-
</div>
|
|
118
|
-
</main>
|
|
119
|
-
);
|
|
9
|
+
return <TenantDashboard appName={appName} session={session} />;
|
|
120
10
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
|
-
import { createIdempotencyKey } from "@/mw/client";
|
|
3
|
+
import { createIdempotencyKey, mwAppId } from "@/mw/client";
|
|
4
4
|
import { createTemplateMockMinuteWorkClient } from "@/mw/mock";
|
|
5
5
|
|
|
6
6
|
describe("MinuteWork SDK mock substrate", () => {
|
|
@@ -17,5 +17,6 @@ describe("MinuteWork SDK mock substrate", () => {
|
|
|
17
17
|
|
|
18
18
|
expect(queryResult.count).toBe(1);
|
|
19
19
|
expect(actionResult.created).toBe(true);
|
|
20
|
+
expect(client.appId).toBe(mwAppId);
|
|
20
21
|
});
|
|
21
22
|
});
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import { createMockMinuteWorkClient } from "@minutework/web-auth/mock";
|
|
6
6
|
|
|
7
|
+
import { mwAppId } from "@/mw/client";
|
|
8
|
+
|
|
7
9
|
export function createTemplateMockMinuteWorkClient() {
|
|
8
10
|
return createMockMinuteWorkClient({
|
|
9
|
-
appId:
|
|
11
|
+
appId: mwAppId,
|
|
10
12
|
verified: true,
|
|
11
13
|
queryHandlers: {
|
|
12
14
|
"demo.list": () => ({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
@@ -22,6 +22,38 @@ const requiredPaths = [
|
|
|
22
22
|
"src/mw/provider.tsx",
|
|
23
23
|
"src/mw/mock.ts",
|
|
24
24
|
];
|
|
25
|
+
const appRoutesRoot = path.join(templateRoot, "src", "app", "app");
|
|
26
|
+
const appLayoutPath = path.join(appRoutesRoot, "layout.tsx");
|
|
27
|
+
const appLayoutSource = readFileSync(appLayoutPath, "utf8");
|
|
28
|
+
const appRoutePagePaths = collectNamedFiles(appRoutesRoot, new Set(["page.tsx"]));
|
|
29
|
+
const appRouteHandlerPaths = collectNamedFiles(appRoutesRoot, new Set(["route.ts", "route.tsx"]));
|
|
30
|
+
const guardedSurfaceSourcePaths = [
|
|
31
|
+
...collectSourceFiles(appRoutesRoot),
|
|
32
|
+
...collectSourceFiles(path.join(templateRoot, "src", "features")),
|
|
33
|
+
];
|
|
34
|
+
const allowedGuardPatternFiles = new Map([
|
|
35
|
+
[
|
|
36
|
+
"src/app/app/layout.tsx",
|
|
37
|
+
new Set(["AuthenticatedAppLayoutShell"]),
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"src/features/auth/components/login-screen.tsx",
|
|
41
|
+
new Set(["useMinuteWorkAuth"]),
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
"src/features/shell/components/authenticated-app-layout-shell.tsx",
|
|
45
|
+
new Set([
|
|
46
|
+
"AuthenticatedAppLayoutShell",
|
|
47
|
+
"useMinuteWorkAuth",
|
|
48
|
+
"useMinuteWorkSession",
|
|
49
|
+
]),
|
|
50
|
+
],
|
|
51
|
+
]);
|
|
52
|
+
const forbiddenGuardPatterns = [
|
|
53
|
+
"useMinuteWorkSession",
|
|
54
|
+
"useMinuteWorkAuth",
|
|
55
|
+
"AuthenticatedAppLayoutShell",
|
|
56
|
+
];
|
|
25
57
|
|
|
26
58
|
const missingPaths = requiredPaths.filter(
|
|
27
59
|
(relativePath) => !existsSync(path.join(templateRoot, relativePath)),
|
|
@@ -35,4 +67,91 @@ if (missingPaths.length > 0) {
|
|
|
35
67
|
);
|
|
36
68
|
}
|
|
37
69
|
|
|
70
|
+
if (!appLayoutSource.includes("AuthenticatedAppLayoutShell")) {
|
|
71
|
+
throw new Error("src/app/app/layout.tsx must wrap all /app routes in AuthenticatedAppLayoutShell.");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!appLayoutSource.includes("{children}")) {
|
|
75
|
+
throw new Error("src/app/app/layout.tsx must pass children through the authenticated layout guard.");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const appRouteRelativePaths = appRoutePagePaths.map((absolutePath) =>
|
|
79
|
+
path.relative(templateRoot, absolutePath),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (!appRouteRelativePaths.includes(path.join("src", "app", "app", "demo", "page.tsx"))) {
|
|
83
|
+
throw new Error("Route contract must include /app/demo so the guarded layout cannot be bypassed.");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (appRouteHandlerPaths.length > 0) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Route handlers under /app bypass the authenticated layout guard.\n${appRouteHandlerPaths
|
|
89
|
+
.map((absolutePath) => `- ${path.relative(templateRoot, absolutePath)}`)
|
|
90
|
+
.join("\n")}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const sourcePath of guardedSurfaceSourcePaths) {
|
|
95
|
+
const source = readFileSync(sourcePath, "utf8");
|
|
96
|
+
const relativePath = normalizePath(path.relative(templateRoot, sourcePath));
|
|
97
|
+
const allowedPatterns = allowedGuardPatternFiles.get(relativePath) ?? new Set();
|
|
98
|
+
const matchedPattern = forbiddenGuardPatterns.find(
|
|
99
|
+
(pattern) => source.includes(pattern) && !allowedPatterns.has(pattern),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (matchedPattern) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`${relativePath} must not own guarded-surface auth wiring (${matchedPattern}); keep raw SDK auth/session hooks in src/features/shell/components/authenticated-app-layout-shell.tsx or the public login screen.`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
38
109
|
console.log("combined route contract is valid");
|
|
110
|
+
|
|
111
|
+
function collectNamedFiles(directory, names) {
|
|
112
|
+
const entries = readdirSync(directory, { withFileTypes: true });
|
|
113
|
+
const files = [];
|
|
114
|
+
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const absolutePath = path.join(directory, entry.name);
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
files.push(...collectNamedFiles(absolutePath, names));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!entry.isFile() || !names.has(entry.name)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!statSync(absolutePath).isFile()) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
files.push(absolutePath);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return files;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function collectSourceFiles(directory) {
|
|
134
|
+
const entries = readdirSync(directory, { withFileTypes: true });
|
|
135
|
+
const files = [];
|
|
136
|
+
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
const absolutePath = path.join(directory, entry.name);
|
|
139
|
+
if (entry.isDirectory()) {
|
|
140
|
+
files.push(...collectSourceFiles(absolutePath));
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!entry.isFile() || !/\.(tsx|ts)$/.test(entry.name)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!statSync(absolutePath).isFile()) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
files.push(absolutePath);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return files;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function normalizePath(value) {
|
|
156
|
+
return value.split(path.sep).join("/");
|
|
157
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { createServer } from "node:http";
|
|
3
2
|
|
|
4
3
|
const [command, ...args] = process.argv.slice(2);
|
|
5
4
|
const storybookHeapMb = process.env.MW_STORYBOOK_NODE_HEAP_MB ?? "768";
|
|
@@ -9,123 +8,38 @@ if (!command) {
|
|
|
9
8
|
process.exit(1);
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
footerBlurb: "Validation fixture content.",
|
|
18
|
-
primaryCta: {
|
|
19
|
-
label: "Sign In",
|
|
20
|
-
href: "/login",
|
|
21
|
-
},
|
|
22
|
-
secondaryCta: {
|
|
23
|
-
label: "Docs",
|
|
24
|
-
href: "/docs",
|
|
25
|
-
},
|
|
26
|
-
primaryNavigation: [
|
|
27
|
-
{ label: "Home", href: "/" },
|
|
28
|
-
{ label: "Pricing", href: "/pricing" },
|
|
29
|
-
{ label: "Docs", href: "/docs" },
|
|
30
|
-
{ label: "Blog", href: "/blog" },
|
|
31
|
-
],
|
|
32
|
-
collections: {
|
|
33
|
-
docs: {
|
|
34
|
-
eyebrow: "Docs",
|
|
35
|
-
title: "Documentation",
|
|
36
|
-
description: "Validation fixture docs collection.",
|
|
37
|
-
},
|
|
38
|
-
blog: {
|
|
39
|
-
eyebrow: "Blog",
|
|
40
|
-
title: "Updates",
|
|
41
|
-
description: "Validation fixture blog collection.",
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
marketingPages: [],
|
|
46
|
-
docs: [],
|
|
47
|
-
blog: [],
|
|
11
|
+
const childEnv = {
|
|
12
|
+
...process.env,
|
|
13
|
+
NEXT_PUBLIC_MW_APP_ID: process.env.NEXT_PUBLIC_MW_APP_ID ?? "tenant.app",
|
|
14
|
+
MW_TEMPLATE_APP_NAME: process.env.MW_TEMPLATE_APP_NAME ?? "Tenant App",
|
|
15
|
+
MW_PUBLIC_BASE_URL: process.env.MW_PUBLIC_BASE_URL ?? "https://public.example.com",
|
|
48
16
|
};
|
|
49
17
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const environment = requestUrl.searchParams.get("environment") ?? "preview";
|
|
62
|
-
const sourceBoundary = environment === "live" ? "runtime_live" : "runtime_preview";
|
|
18
|
+
if (
|
|
19
|
+
command === "storybook" &&
|
|
20
|
+
!/\bmax-old-space-size=/.test(childEnv.NODE_OPTIONS ?? "")
|
|
21
|
+
) {
|
|
22
|
+
childEnv.NODE_OPTIONS = [childEnv.NODE_OPTIONS, `--max-old-space-size=${storybookHeapMb}`]
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.join(" ");
|
|
25
|
+
}
|
|
63
26
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
environment,
|
|
68
|
-
source_boundary: sourceBoundary,
|
|
69
|
-
snapshot: emptySnapshot,
|
|
70
|
-
}),
|
|
71
|
-
);
|
|
27
|
+
const child = spawn(command, args, {
|
|
28
|
+
env: childEnv,
|
|
29
|
+
stdio: "inherit",
|
|
72
30
|
});
|
|
73
31
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const address = server.address();
|
|
79
|
-
if (!address || typeof address === "string") {
|
|
80
|
-
reject(new Error("Unable to resolve validation fixture server address."));
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
resolve(`http://127.0.0.1:${address.port}`);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const exitCode = await new Promise((resolve, reject) => {
|
|
88
|
-
const childEnv = {
|
|
89
|
-
...process.env,
|
|
90
|
-
MW_CONTENT_API_TOKEN: "validate-content-token",
|
|
91
|
-
MW_PLATFORM_BASE_URL: baseUrl,
|
|
92
|
-
MW_PUBLIC_CONTENT_SOURCE: "minutework_cms",
|
|
93
|
-
MW_PUBLIC_BASE_URL: "https://public.example.com",
|
|
94
|
-
MW_PUBLIC_SITE_ENV: "preview",
|
|
95
|
-
MW_PUBLIC_SITE_PROPERTY_KEY: "main-site",
|
|
96
|
-
};
|
|
97
|
-
if (
|
|
98
|
-
command === "storybook" &&
|
|
99
|
-
!/\bmax-old-space-size=/.test(childEnv.NODE_OPTIONS ?? "")
|
|
100
|
-
) {
|
|
101
|
-
childEnv.NODE_OPTIONS = [childEnv.NODE_OPTIONS, `--max-old-space-size=${storybookHeapMb}`]
|
|
102
|
-
.filter(Boolean)
|
|
103
|
-
.join(" ");
|
|
104
|
-
}
|
|
105
|
-
const child = spawn(command, args, {
|
|
106
|
-
env: childEnv,
|
|
107
|
-
stdio: "inherit",
|
|
108
|
-
});
|
|
32
|
+
child.once("error", (error) => {
|
|
33
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
});
|
|
109
36
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
resolve(code ?? 1);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
37
|
+
child.once("exit", (code, signal) => {
|
|
38
|
+
if (signal) {
|
|
39
|
+
console.error(`Validation fixture command exited via signal ${signal}.`);
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
119
43
|
|
|
120
|
-
process.exitCode =
|
|
121
|
-
}
|
|
122
|
-
await new Promise((resolve, reject) => {
|
|
123
|
-
server.close((error) => {
|
|
124
|
-
if (error) {
|
|
125
|
-
reject(error);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
resolve(undefined);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
}
|
|
44
|
+
process.exitCode = code ?? 1;
|
|
45
|
+
});
|
|
@@ -11,13 +11,9 @@ export default defineConfig({
|
|
|
11
11
|
},
|
|
12
12
|
test: {
|
|
13
13
|
env: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
MW_CONTENT_API_TOKEN: "test-content-token",
|
|
14
|
+
NEXT_PUBLIC_MW_APP_ID: "tenant.app",
|
|
15
|
+
MW_TEMPLATE_APP_NAME: "Tenant App",
|
|
17
16
|
MW_PUBLIC_BASE_URL: "http://127.0.0.1:3000",
|
|
18
|
-
MW_PUBLIC_SITE_PROPERTY_KEY: "main-site",
|
|
19
|
-
MW_PUBLIC_SITE_ENV: "preview",
|
|
20
|
-
MW_STATIC_PUBLIC_CONTENT_PATH: "content/public-site.json",
|
|
21
17
|
},
|
|
22
18
|
environment: "node",
|
|
23
19
|
globals: true,
|
|
@@ -11,12 +11,9 @@ export async function bootstrapVmLocalWorkspaceEnv(options) {
|
|
|
11
11
|
}
|
|
12
12
|
if (options.starters.tenantApp) {
|
|
13
13
|
await writeManagedEnvBlock(path.join(options.workspaceRoot, "tenant-app", ".env.local"), "tenant-app", {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
NEXT_PUBLIC_MW_APP_ID: workspaceWiring.app_id ?? "tenant.app",
|
|
15
|
+
MW_TEMPLATE_APP_NAME: workspaceWiring.workspace_name ?? "Tenant App",
|
|
16
16
|
MW_PUBLIC_BASE_URL: workspaceWiring.public_base_url ?? "",
|
|
17
|
-
MW_PUBLIC_SITE_PROPERTY_KEY: workspaceWiring.public_site_property_key ?? "",
|
|
18
|
-
MW_PUBLIC_SITE_ENV: workspaceWiring.public_site_env ?? "preview",
|
|
19
|
-
MW_ENABLE_RUNTIME_COMMAND_EXAMPLE: "false",
|
|
20
17
|
});
|
|
21
18
|
}
|
|
22
19
|
if (options.starters.sidecar) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-bootstrap.js","sourceRoot":"","sources":["../src/workspace-bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"workspace-bootstrap.js","sourceRoot":"","sources":["../src/workspace-bootstrap.ts"],"names":[],"mappings":"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"}
|