create-bw-app 0.26.2 → 0.27.1

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
@@ -106,3 +106,36 @@ The scaffold records `SUPABASE_PROJECT_REGION` in `.env.local` and infrastructur
106
106
  - `packages/create-bw-app/template/site/base`
107
107
  - `packages/create-bw-app/template/modules`
108
108
  - `packages/create-bw-app/template/supabase`
109
+
110
+ ### Opt-in social media viewer
111
+
112
+ For a platform app with marketing enabled, first install a marketing release that
113
+ exports `social-media` and `withSocialMediaNavigation`, then run:
114
+
115
+ ```sh
116
+ bw setup social-media --dry-run
117
+ bw setup social-media
118
+ ```
119
+
120
+ Use `--target-dir <path>` to select another app directory. Setup creates
121
+ `config/social-media-plan.json` with an empty, neutral plan and the thin page at
122
+ `app/(shell)/marketing/social-media/page.tsx`. All client copy belongs in that
123
+ app-owned JSON; components, styles, validation, and interactions stay in marketing.
124
+ No database or publishing service is created.
125
+
126
+ Setup appends navigation wiring to the standard app-owned
127
+ `config/shell.overrides.ts`, which must declare
128
+ `export const shellRegistrationOverrides: ShellRegistrationOverrides = { ... };`.
129
+ Existing marketing overrides are composed through the shared helper:
130
+
131
+ ```ts
132
+ import { withSocialMediaNavigation } from "@brightweblabs/module-marketing/registration";
133
+ shellRegistrationOverrides.marketing = withSocialMediaNavigation(shellRegistrationOverrides.marketing);
134
+ ```
135
+
136
+ Custom shell export shapes require manual wiring before adopting the standard
137
+ shape. Setup checks prerequisites and paths before writing and rejects symlinks
138
+ on generated paths. Existing JSON and page files are never replaced, even when
139
+ empty or customized; reruns preserve them. These files are not managed templates
140
+ or refreshable starters, and `bw update --refresh-starters` preserves them and
141
+ shell overrides. New marketing apps do not receive this feature until opted in.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-bw-app",
3
3
  "private": false,
4
- "version": "0.26.2",
4
+ "version": "0.27.1",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-bw-app": "bin/create-bw-app.mjs",
package/src/bw.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { setupBrightwebFeature } from "./setup.mjs";
1
2
  import { addBrightwebModule } from "./add.mjs";
2
3
  import { createFirstAdmin } from "./admin.mjs";
3
4
  import { adoptBrightwebApp } from "./adopt.mjs";
@@ -8,7 +9,7 @@ import { scaffoldBrightwebApp } from "./scaffold-cmd.mjs";
8
9
  import { updateBrightwebApp } from "./update.mjs";
9
10
  import { upgradeBrightwebApp } from "./upgrade.mjs";
10
11
 
11
- const HELP = `Usage: bw <command> [options]\n\nCommands:\n add <moduleKey> Install a module and its requirements\n admin create Bootstrap a passwordless administrator safely\n adopt Create an honest manifest for a legacy app\n diff <relpath> Compare a tracked scaffold file with its template\n scaffold <action> List or record per-file scaffold intent\n remove <moduleKey> Conservatively remove module package wiring\n upgrade [moduleKey] Upgrade packages, managed files, and migrations\n update Alias for the legacy create-bw-app update flow\n doctor Validate app health and manifest consistency\n\nRun bw <command> --help for command-specific options.`;
12
+ const HELP = `Usage: bw <command> [options]\n\nCommands:\n setup social-media Create app-owned social media content and wiring\n add <moduleKey> Install a module and its requirements\n admin create Bootstrap a passwordless administrator safely\n adopt Create an honest manifest for a legacy app\n diff <relpath> Compare a tracked scaffold file with its template\n scaffold <action> List or record per-file scaffold intent\n remove <moduleKey> Conservatively remove module package wiring\n upgrade [moduleKey] Upgrade packages, managed files, and migrations\n update Alias for the legacy create-bw-app update flow\n doctor Validate app health and manifest consistency\n\nRun bw <command> --help for command-specific options.`;
12
13
 
13
14
  function parseOptions(argv) {
14
15
  const options = {};
@@ -36,7 +37,8 @@ export async function runBwCli(argv = process.argv.slice(2), runtimeOptions = {}
36
37
  if (!command || command === "--help" || command === "help") { process.stdout.write(`${HELP}\n`); return; }
37
38
  const { options, positionals } = parseOptions(argv.slice(1));
38
39
  try {
39
- if (command === "add") await addBrightwebModule(positionals[0], options, runtimeOptions);
40
+ if (command === "setup") await setupBrightwebFeature(positionals[0], options, runtimeOptions);
41
+ else if (command === "add") await addBrightwebModule(positionals[0], options, runtimeOptions);
40
42
  else if (command === "admin") await createFirstAdmin(positionals[0], options, runtimeOptions);
41
43
  else if (command === "adopt") await adoptBrightwebApp(options, runtimeOptions);
42
44
  else if (command === "diff") await diffBrightwebScaffold(positionals[0], options, runtimeOptions);
package/src/constants.mjs CHANGED
@@ -186,16 +186,16 @@ export const PLATFORM_STARTER_FILES = [
186
186
  ];
187
187
 
188
188
  export const APP_DEPENDENCY_DEFAULTS = {
189
- "@brightweblabs/app-shell": "^0.16.1",
190
- "@brightweblabs/core-auth": "^0.12.0",
189
+ "@brightweblabs/app-shell": "^0.16.2",
190
+ "@brightweblabs/core-auth": "^0.12.1",
191
191
  "@brightweblabs/infra": "^0.7.0",
192
- "@brightweblabs/module-admin": "^0.9.5",
193
- "@brightweblabs/module-crm": "^0.18.2",
194
- "@brightweblabs/module-marketing": "^0.4.25",
195
- "@brightweblabs/module-orgs": "^0.7.1",
196
- "@brightweblabs/module-projects": "^0.19.1",
197
- "@brightweblabs/theme": "^0.8.3",
198
- "@brightweblabs/ui": "^1.5.4",
192
+ "@brightweblabs/module-admin": "^0.9.6",
193
+ "@brightweblabs/module-crm": "^0.18.3",
194
+ "@brightweblabs/module-marketing": "^0.5.1",
195
+ "@brightweblabs/module-orgs": "^0.7.2",
196
+ "@brightweblabs/module-projects": "^0.19.2",
197
+ "@brightweblabs/theme": "^0.8.4",
198
+ "@brightweblabs/ui": "^1.5.5",
199
199
  "geist": "1.7.2",
200
200
  "lucide-react": "^1.8.0",
201
201
  "next": "^16.0.0",
package/src/setup.mjs ADDED
@@ -0,0 +1,96 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { readAppManifest, readConfiguredModuleFlags } from "./app-manifest.mjs";
5
+
6
+ const HELP = `Usage: bw setup social-media [options]
7
+
8
+ Create an app-owned empty social media plan, page, and navigation.
9
+ Existing plan and page files are preserved. Requires installed marketing support.
10
+
11
+ Options:
12
+ --target-dir <path> App directory (defaults to cwd)
13
+ --dry-run Show changes without writing
14
+ --help Show this help`;
15
+ const PLAN_PATH = "config/social-media-plan.json";
16
+ const PAGE_PATH = "app/(shell)/marketing/social-media/page.tsx";
17
+ const SHELL_PATH = "config/shell.overrides.ts";
18
+ const ASSIGNMENT = "shellRegistrationOverrides.marketing = withSocialMediaNavigation(shellRegistrationOverrides.marketing);";
19
+ const IMPORT = 'import { withSocialMediaNavigation } from "@brightweblabs/module-marketing/registration";';
20
+ const PLAN = { title: "Social media", period: "", sections: [{ id: "calendario", title: "Calendário" }], types: {}, events: [] };
21
+ const PAGE = `import { SocialMediaPage } from "@brightweblabs/module-marketing/social-media";
22
+ import plan from "../../../../config/social-media-plan.json";
23
+
24
+ export default function Page() {
25
+ return <SocialMediaPage plan={plan} />;
26
+ }
27
+ `;
28
+
29
+ // Generated paths must remain real directories/files inside the app, even when
30
+ // an intermediate directory or an existing target is a symlink.
31
+ async function inspectTarget(root, relativePath) {
32
+ let current = root;
33
+ const parts = relativePath.split("/");
34
+ for (const [index, part] of parts.entries()) {
35
+ current = path.join(current, part);
36
+ let stat;
37
+ try { stat = await fs.lstat(current); } catch (error) {
38
+ if (error.code === "ENOENT") return null;
39
+ throw error;
40
+ }
41
+ if (stat.isSymbolicLink()) throw new Error(`Setup does not follow symlinks: ${relativePath}`);
42
+ if (index < parts.length - 1 && !stat.isDirectory()) throw new Error(`Expected a directory at ${current}`);
43
+ if (index === parts.length - 1 && !stat.isFile()) throw new Error(`Expected a file at ${relativePath}`);
44
+ }
45
+ return fs.readFile(current, "utf8");
46
+ }
47
+
48
+ function navigationSource(source) {
49
+ const code = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
50
+ if (!/^export const shellRegistrationOverrides\s*:\s*ShellRegistrationOverrides\s*=\s*\{/m.test(code)) {
51
+ throw new Error(`Cannot safely identify the standard exported const shellRegistrationOverrides: ShellRegistrationOverrides in ${SHELL_PATH}. Wire withSocialMediaNavigation manually before rerunning setup.`);
52
+ }
53
+ if (code.includes(ASSIGNMENT) && code.includes(IMPORT)) return source;
54
+ if (/\bwithSocialMediaNavigation\b/.test(code)) {
55
+ throw new Error(`Existing custom withSocialMediaNavigation wiring in ${SHELL_PATH}; use the documented import and assignment before rerunning setup.`);
56
+ }
57
+ return `${source}${source.endsWith("\n") ? "" : "\n"}\n${IMPORT}\n${ASSIGNMENT}\n`;
58
+ }
59
+
60
+ export async function setupBrightwebFeature(feature, options = {}, runtimeOptions = {}) {
61
+ if (options.help) { process.stdout.write(`${HELP}\n`); return; }
62
+ if (feature !== "social-media") throw new Error(`Unknown setup feature: ${feature || "(missing)"}\n${HELP}`);
63
+ const targetDir = await fs.realpath(path.resolve(runtimeOptions.targetDir || options.targetDir || process.cwd()));
64
+ const manifest = await readAppManifest(targetDir);
65
+ const flags = await readConfiguredModuleFlags(targetDir);
66
+ if (manifest.app.template !== "platform" || !manifest.modules.marketing || flags.marketing !== true) {
67
+ throw new Error("Social media setup requires a platform app with marketing installed and enabled. Run bw add marketing first.");
68
+ }
69
+ try {
70
+ const require = createRequire(path.join(targetDir, "package.json"));
71
+ require.resolve("@brightweblabs/module-marketing/social-media");
72
+ const registration = await fs.readFile(require.resolve("@brightweblabs/module-marketing/registration"), "utf8");
73
+ if (!/export\s+(?:function|const)\s+withSocialMediaNavigation\b/.test(registration)) throw new Error("Missing navigation helper");
74
+ } catch {
75
+ throw new Error("Install or upgrade @brightweblabs/module-marketing to a release with social-media and withSocialMediaNavigation support, then rerun setup.");
76
+ }
77
+ const plan = await inspectTarget(targetDir, PLAN_PATH);
78
+ const page = await inspectTarget(targetDir, PAGE_PATH);
79
+ const shell = await inspectTarget(targetDir, SHELL_PATH);
80
+ if (shell === null) throw new Error(`Missing ${SHELL_PATH}; restore the app-owned shell overrides file before setup.`);
81
+ const nextShell = navigationSource(shell);
82
+ const changes = [
83
+ ...(plan === null ? [{ path: PLAN_PATH, content: `${JSON.stringify(PLAN, null, 2)}\n`, create: true }] : []),
84
+ ...(page === null ? [{ path: PAGE_PATH, content: PAGE, create: true }] : []),
85
+ ...(nextShell !== shell ? [{ path: SHELL_PATH, content: nextShell, create: false }] : []),
86
+ ];
87
+ for (const change of changes) {
88
+ process.stdout.write(`${options.dryRun ? "Would " : ""}${change.create ? "Create" : "Update"} ${change.path}\n`);
89
+ }
90
+ if (!options.dryRun) for (const change of changes) {
91
+ await fs.mkdir(path.dirname(path.join(targetDir, change.path)), { recursive: true });
92
+ await fs.writeFile(path.join(targetDir, change.path), change.content, { flag: change.create ? "wx" : "w" });
93
+ }
94
+ process.stdout.write(changes.length ? "Social media setup ready. Content belongs to this app.\n" : "Social media is already configured; existing files preserved.\n");
95
+ return { changes: changes.map(({ path: relativePath }) => relativePath), dryRun: !!options.dryRun };
96
+ }
@@ -1,3 +1,5 @@
1
+ @layer theme, base, components, utilities;
2
+
1
3
  @import "tailwindcss";
2
4
  @import "@brightweblabs/theme/css";
3
5
  @import "./theme.css";
@@ -1,3 +1,5 @@
1
+ @layer theme, base, components, utilities;
2
+
1
3
  @import "tailwindcss";
2
4
 
3
5
  @theme inline {