create-bw-app 0.9.4 → 0.9.5

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/README.md CHANGED
@@ -41,6 +41,8 @@ pnpm dlx create-bw-app update --target-dir ./apps/client-portal
41
41
  Current updater behavior:
42
42
 
43
43
  - updates installed `@brightweblabs/*` packages only
44
+ - in published mode, resolves those `@brightweblabs/*` target versions from npm at update time
45
+ - fails the update if npm resolution fails unless you pass `--allow-stale-fallback`
44
46
  - re-syncs managed BrightWeb config files such as `next.config.ts`, `config/modules.ts`, and `config/shell.ts`
45
47
  - reports missing or drifted starter files and only rewrites them with `--refresh-starters`
46
48
  - prints the follow-up install command unless `--install` is passed
@@ -54,6 +56,7 @@ Current updater behavior:
54
56
  - prompts to install dependencies immediately
55
57
  - copies a clean Next.js App Router starter template
56
58
  - platform apps include BrightWeb auth, shell wiring, and optional module starter surfaces
59
+ - platform apps include a local `components/` folder for app-owned UI alongside the shared BrightWeb packages
57
60
  - site apps include Next.js, Tailwind CSS v4, and local component primitives
58
61
  - writes `package.json`, `next.config.ts`, `.gitignore`, and `README.md` for both templates
59
62
  - platform apps also write `.env.local`, `AGENTS.md`, `docs/ai/README.md`, `docs/ai/examples.md`, `docs/ai/app-context.json`, and generated config files for brand and module state
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-bw-app",
3
3
  "private": false,
4
- "version": "0.9.4",
4
+ "version": "0.9.5",
5
5
  "type": "module",
6
6
  "bin": "bin/create-bw-app.mjs",
7
7
  "files": [
package/src/cli.mjs CHANGED
@@ -42,6 +42,11 @@ function parseArgv(argv) {
42
42
  continue;
43
43
  }
44
44
 
45
+ if (token === "--allow-stale-fallback") {
46
+ options.allowStaleFallback = true;
47
+ continue;
48
+ }
49
+
45
50
  const [rawKey, inlineValue] = token.slice(2).split("=", 2);
46
51
  const key = toCamelCase(rawKey);
47
52
  const nextValue = inlineValue ?? argv[index + 1];
package/src/constants.mjs CHANGED
@@ -136,6 +136,7 @@ Update options:
136
136
  --target-dir <path> Existing app directory to update (defaults to cwd)
137
137
  --workspace-root <path> BrightWeb workspace root for workspace:* apps
138
138
  --package-manager <name> Override package manager: pnpm, npm, yarn, or bun
139
+ --allow-stale-fallback Use baked-in BrightWeb package versions if npm lookup fails
139
140
  --install Run install after writing package changes
140
141
  --refresh-starters Rewrite starter route files from the latest template
141
142
  --dry-run Print the update plan without writing files
package/src/generator.mjs CHANGED
@@ -631,6 +631,7 @@ export function createAppContextFile({
631
631
  ".env.local",
632
632
  ],
633
633
  appRoutesRoot: "app",
634
+ componentsRoot: "components",
634
635
  configRoot: "config",
635
636
  brandAssetsRoot: "public/brand",
636
637
  },
@@ -638,6 +639,7 @@ export function createAppContextFile({
638
639
  ownership: {
639
640
  appOwned: [
640
641
  "app/**",
642
+ "components/**",
641
643
  "config/**",
642
644
  "docs/ai/**",
643
645
  "public/brand/**",
package/src/update.mjs CHANGED
@@ -76,6 +76,62 @@ function collectInstalledBrightwebPackages(manifest) {
76
76
  return installed;
77
77
  }
78
78
 
79
+ async function resolvePublishedBrightwebVersions(installedBrightwebPackages, options = {}) {
80
+ const packageNames = Array.from(installedBrightwebPackages.keys()).sort();
81
+
82
+ if (packageNames.length === 0) {
83
+ return {};
84
+ }
85
+
86
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
87
+ if (typeof fetchImpl !== "function") {
88
+ throw new Error("Published updates require fetch support to resolve BrightWeb package versions from npm.");
89
+ }
90
+
91
+ const fallbackVersionMap = options.fallbackVersionMap || {};
92
+ const allowStaleFallback = options.allowStaleFallback === true;
93
+ const resolvedVersions = {};
94
+ const failures = [];
95
+
96
+ await Promise.all(
97
+ packageNames.map(async (packageName) => {
98
+ try {
99
+ const response = await fetchImpl(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`);
100
+ if (!response?.ok) {
101
+ throw new Error(`npm registry responded with ${response?.status ?? "an unknown error"}`);
102
+ }
103
+
104
+ const payload = await response.json();
105
+ if (!payload?.version || typeof payload.version !== "string") {
106
+ throw new Error("npm registry response did not include a version");
107
+ }
108
+
109
+ resolvedVersions[packageName] = `^${payload.version}`;
110
+ } catch (error) {
111
+ const fallbackVersion = fallbackVersionMap[packageName];
112
+ if (allowStaleFallback && fallbackVersion) {
113
+ resolvedVersions[packageName] = fallbackVersion;
114
+ return;
115
+ }
116
+
117
+ const message = error instanceof Error ? error.message : "Unknown error";
118
+ failures.push(`${packageName}: ${message}`);
119
+ }
120
+ }),
121
+ );
122
+
123
+ if (failures.length > 0) {
124
+ const fallbackHint = allowStaleFallback
125
+ ? "No baked-in fallback version was available for at least one package."
126
+ : "Re-run with --allow-stale-fallback to use the CLI's baked-in BrightWeb package versions instead.";
127
+ throw new Error(
128
+ `Failed to resolve published BrightWeb package versions from npm.\n${failures.join("\n")}\n${fallbackHint}`,
129
+ );
130
+ }
131
+
132
+ return resolvedVersions;
133
+ }
134
+
79
135
  function parseConfiguredModules(content) {
80
136
  const enabledModules = [];
81
137
 
@@ -339,6 +395,13 @@ export async function buildBrightwebAppUpdatePlan(argvOptions = {}, runtimeOptio
339
395
  const packageManager = detectPackageManager(argvOptions.packageManager || runtimeOptions.packageManager);
340
396
  const installedModules = detectInstalledModules(installedBrightwebPackagesMap);
341
397
  const versionMap = await getVersionMap(workspaceRoot);
398
+ const brightwebVersionOverrides = dependencyMode === "published"
399
+ ? await resolvePublishedBrightwebVersions(installedBrightwebPackagesMap, {
400
+ fetchImpl: runtimeOptions.fetchImpl,
401
+ fallbackVersionMap: versionMap,
402
+ allowStaleFallback: argvOptions.allowStaleFallback || runtimeOptions.allowStaleFallback,
403
+ })
404
+ : {};
342
405
  const dbRegistry = await getDbModuleRegistry(workspaceRoot);
343
406
  const dbInstallPlan = template === "platform"
344
407
  ? createDbInstallPlan({
@@ -356,7 +419,10 @@ export async function buildBrightwebAppUpdatePlan(argvOptions = {}, runtimeOptio
356
419
  template,
357
420
  dependencyMode,
358
421
  installedModules,
359
- versionMap,
422
+ versionMap: {
423
+ ...versionMap,
424
+ ...brightwebVersionOverrides,
425
+ },
360
426
  });
361
427
  const packageJsonUpdate = mergeManagedPackageUpdates({
362
428
  manifest,
@@ -8,6 +8,7 @@ This generated project is a BrightWeb platform starter. Use this file as the loc
8
8
  - `docs/ai/README.md`: app-specific routing guide for agents.
9
9
  - `docs/ai/examples.md`: common setup and customization flows.
10
10
  - `docs/ai/app-context.json`: machine-readable app summary for quick discovery.
11
+ - `components/`: local app components used by starter routes and future product surfaces.
11
12
  - `config/brand.ts`: client identity, naming, and contact defaults.
12
13
  - `config/modules.ts`: selected module set and runtime enablement.
13
14
  - `config/client.ts`: starter-facing derived state used by the home page and setup surfaces.
@@ -1,4 +1,4 @@
1
- import { AuthPlayground } from "./auth-playground";
1
+ import { AuthPlayground } from "../../../components/auth-playground";
2
2
 
3
3
  export default function AuthPlaygroundPage() {
4
4
  return <AuthPlayground />;
@@ -1,4 +1,4 @@
1
- import { AppShellPreview } from "../app-shell-preview";
1
+ import { AppShellPreview } from "../../../components/app-shell-preview";
2
2
 
3
3
  export default function AppShellPreviewPage() {
4
4
  return (
@@ -12,8 +12,8 @@ import {
12
12
  type ResolvedClientAppShellConfig,
13
13
  } from "@brightweblabs/app-shell";
14
14
  import { LayoutTemplate, Sparkles, Users } from "lucide-react";
15
- import { starterBrandConfig } from "../../config/brand";
16
- import { getStarterShellConfig } from "../../config/shell";
15
+ import { starterBrandConfig } from "../config/brand";
16
+ import { getStarterShellConfig } from "../config/shell";
17
17
 
18
18
  const mockUser = {
19
19
  email: "admin@starter-client.test",
@@ -9,6 +9,7 @@ It is intentionally app-scoped. It explains the generated project you are in, no
9
9
  This app is a normal Next.js App Router project with BrightWeb runtime wiring layered on top.
10
10
 
11
11
  - `app/`: route tree, layouts, pages, starter previews, and playground routes.
12
+ - `components/`: local React components used by starter routes and app-owned product work.
12
13
  - `config/`: generated app configuration for brand, env readiness, enabled modules, bootstrap content, and shell registration.
13
14
  - `public/brand/`: starter logos used by the shell lockups.
14
15
  - `.env.local`: local service configuration for Supabase, Resend, and runtime URLs.
@@ -18,6 +19,7 @@ This app is a normal Next.js App Router project with BrightWeb runtime wiring la
18
19
  - `docs/ai/app-context.json`: machine-readable summary of this app's template, starter routes, and first-read files.
19
20
  - `docs/ai/examples.md`: common setup and customization workflows.
20
21
  - `README.md`: first-run setup steps.
22
+ - `components/`: local app component layer for starter surfaces and future product UI.
21
23
  - `config/brand.ts`: client name, product name, support inboxes, and brand color.
22
24
  - `config/modules.ts`: module metadata and enablement flags for CRM, Projects, and Admin.
23
25
  - `config/client.ts`: aggregated state consumed by starter pages.
@@ -33,6 +35,7 @@ This app is a normal Next.js App Router project with BrightWeb runtime wiring la
33
35
 
34
36
  - Change client identity first in `config/brand.ts`.
35
37
  - Check module presence in `config/modules.ts` before editing or creating module-specific routes.
38
+ - Add app-specific UI in `components/` before forking shared package code.
36
39
  - Use `config/shell.ts` when navigation or toolbar behavior needs to change.
37
40
  - Use `config/bootstrap.ts` and `config/client.ts` when the setup checklist or readiness messaging is wrong.
38
41
  - Keep starter validation routes until the real product routes replace their purpose.
@@ -18,6 +18,7 @@ Goal: get the generated starter running with real credentials.
18
18
  Goal: update the starter to the real client name and support details.
19
19
 
20
20
  - Edit `config/brand.ts`.
21
+ - Move route-specific presentation into `components/` when the home or preview surfaces need app-owned UI.
21
22
  - Check `config/client.ts` or `config/bootstrap.ts` if starter copy still references old defaults.
22
23
  - Validate the home page and `/preview/app-shell` after the change.
23
24
 
@@ -26,6 +27,7 @@ Goal: update the starter to the real client name and support details.
26
27
  Goal: move from validation surfaces to product-owned pages.
27
28
 
28
29
  - Build the real routes in `app/` first.
30
+ - Keep reusable route UI in `components/` so the app follows the expected Next.js folder split.
29
31
  - Update `config/shell.ts` if navigation or toolbar behavior changes.
30
32
  - Remove `/bootstrap`, `/preview/app-shell`, or `/playground/*` only after links and config references are cleaned up.
31
33
 
@@ -0,0 +1,13 @@
1
+ type RouteHandler = (request: Request) => Response | Promise<Response>;
2
+
3
+ type ModuleWithHandler<THandlerName extends string> = Record<THandlerName, RouteHandler>;
4
+
5
+ export function createModuleRouteHandler<THandlerName extends string>(
6
+ loadModule: () => Promise<ModuleWithHandler<THandlerName>>,
7
+ handlerName: THandlerName,
8
+ ): RouteHandler {
9
+ return async function moduleRouteHandler(request: Request) {
10
+ const module = await loadModule();
11
+ return module[handlerName](request);
12
+ };
13
+ }
@@ -1,6 +1,8 @@
1
+ import { createModuleRouteHandler } from "../_shared/create-module-route-handler";
2
+
1
3
  export const dynamic = "force-dynamic";
2
4
 
3
- export async function GET(request: Request) {
4
- const { handleCrmContactsGetRequest } = await import("@brightweblabs/module-crm");
5
- return handleCrmContactsGetRequest(request);
6
- }
5
+ export const GET = createModuleRouteHandler(
6
+ () => import("@brightweblabs/module-crm"),
7
+ "handleCrmContactsGetRequest",
8
+ );
@@ -1,6 +1,8 @@
1
+ import { createModuleRouteHandler } from "../_shared/create-module-route-handler";
2
+
1
3
  export const dynamic = "force-dynamic";
2
4
 
3
- export async function GET(request: Request) {
4
- const { handleCrmOrganizationsGetRequest } = await import("@brightweblabs/module-crm");
5
- return handleCrmOrganizationsGetRequest(request);
6
- }
5
+ export const GET = createModuleRouteHandler(
6
+ () => import("@brightweblabs/module-crm"),
7
+ "handleCrmOrganizationsGetRequest",
8
+ );
@@ -1,6 +1,8 @@
1
+ import { createModuleRouteHandler } from "../_shared/create-module-route-handler";
2
+
1
3
  export const dynamic = "force-dynamic";
2
4
 
3
- export async function GET(request: Request) {
4
- const { handleCrmOwnersGetRequest } = await import("@brightweblabs/module-crm");
5
- return handleCrmOwnersGetRequest(request);
6
- }
5
+ export const GET = createModuleRouteHandler(
6
+ () => import("@brightweblabs/module-crm"),
7
+ "handleCrmOwnersGetRequest",
8
+ );
@@ -1,6 +1,8 @@
1
+ import { createModuleRouteHandler } from "../_shared/create-module-route-handler";
2
+
1
3
  export const dynamic = "force-dynamic";
2
4
 
3
- export async function GET(request: Request) {
4
- const { handleCrmStatsGetRequest } = await import("@brightweblabs/module-crm");
5
- return handleCrmStatsGetRequest(request);
6
- }
5
+ export const GET = createModuleRouteHandler(
6
+ () => import("@brightweblabs/module-crm"),
7
+ "handleCrmStatsGetRequest",
8
+ );