basebrick-bricklayer 1.1.0 → 1.2.0

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
@@ -99,6 +99,23 @@ Bricklayer natively supports reading `.env` and `.dev.vars` (Cloudflare Pages) f
99
99
  - **Nunjucks Templates:** Variables are automatically passed to Nunjucks templates under the global `env` object. E.g., `{{ env.MY_API_KEY }}`.
100
100
  - **CMS Configuration:** Environment variables can be directly interpolated in `generic.json` strings using the `${VARIABLE_NAME}` syntax.
101
101
 
102
+ ## Bricklayer Manager
103
+
104
+ Bricklayer includes a centralized dashboard to help you track and administer all your statically generated sites.
105
+
106
+ The **Bricklayer Manager** allows you to:
107
+ - View all deployed Bricklayer sites via an elegant web UI.
108
+ - Automatically track and store Cloudflare Worker endpoints.
109
+ - Access deep links directly to Cloudflare Dashboards for easy debugging.
110
+
111
+ You can find the source code and instructions for deploying your own manager instance in the `manager/` directory of the core [BrickLayer GitHub Repository](https://github.com/cryptoskillz/BrickLayer/tree/main/manager).
112
+
113
+ Once your manager is deployed, use the CLI command to securely sync any of your projects:
114
+
115
+ ```bash
116
+ bricklayer manage
117
+ ```
118
+
102
119
  ## Directory Structure
103
120
 
104
121
  Bricklayer expects the following default structure (overridable via options):
package/bin/bricklayer.js CHANGED
@@ -15,6 +15,7 @@ Commands:
15
15
  (empty) Run a standard development build
16
16
  init Scaffold a new project (Demo, CMS, Cloudflare API)
17
17
  manage Register this project with a Bricklayer Manager
18
+ deploy Deploy the project to Cloudflare and register its URL
18
19
  help Show this help message
19
20
 
20
21
  Options:
@@ -36,8 +37,21 @@ Generated NPM Scripts (after init):
36
37
 
37
38
  if (args.includes('init')) {
38
39
  initProject(process.cwd()).catch(console.error);
40
+ } else if (args.includes('deploy')) {
41
+ const preview = args.includes('--preview');
42
+ import('./deploy.js').then(m => m.deployProject(process.cwd(), { preview })).catch(console.error);
39
43
  } else if (args.includes('manage')) {
40
- import('./manage.js').then(m => m.manageProject(process.cwd())).catch(console.error);
44
+ const reconfigure = args.includes('-c') || args.includes('--config');
45
+ let url = null;
46
+ let token = null;
47
+
48
+ const uIndex = args.findIndex(a => a === '-u' || a === '--url');
49
+ if (uIndex !== -1 && args[uIndex + 1]) url = args[uIndex + 1];
50
+
51
+ const tIndex = args.findIndex(a => a === '-t' || a === '--token');
52
+ if (tIndex !== -1 && args[tIndex + 1]) token = args[tIndex + 1];
53
+
54
+ import('./manage.js').then(m => m.manageProject(process.cwd(), { reconfigure, url, token })).catch(console.error);
41
55
  } else {
42
56
  const isProd = args.includes('--prod');
43
57
  buildSite({ isProd, cwd: process.cwd() })
package/bin/deploy.js ADDED
@@ -0,0 +1,93 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { spawn } from 'child_process';
4
+
5
+ export async function deployProject(cwd, options = {}) {
6
+ const isPreview = options.preview || false;
7
+
8
+ console.log(`\nšŸš€ Deploying to Cloudflare ${isPreview ? 'Preview' : 'Production'}...\n`);
9
+
10
+ const args = ['deploy'];
11
+ if (isPreview) {
12
+ args.push('--env', 'preview');
13
+ }
14
+
15
+ const tmpLog = path.join(cwd, '.wrangler-deploy.log');
16
+
17
+ let commandStr;
18
+ if (process.platform === 'darwin') {
19
+ commandStr = `script -q ${tmpLog} npx wrangler ${args.join(' ')}`;
20
+ } else if (process.platform === 'linux') {
21
+ commandStr = `script -q -c "npx wrangler ${args.join(' ')}" ${tmpLog}`;
22
+ } else {
23
+ // Windows fallback
24
+ commandStr = `npx wrangler ${args.join(' ')} > ${tmpLog} 2>&1`;
25
+ }
26
+
27
+ const child = spawn(commandStr, {
28
+ cwd,
29
+ stdio: 'inherit',
30
+ shell: true
31
+ });
32
+
33
+ child.on('close', async (code) => {
34
+ let outputData = '';
35
+ if (fs.existsSync(tmpLog)) {
36
+ outputData = fs.readFileSync(tmpLog, 'utf8');
37
+ // Remove ANSI color codes
38
+ outputData = outputData.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
39
+ // Clean up the log file
40
+ try { fs.unlinkSync(tmpLog); } catch (e) {}
41
+ }
42
+
43
+ if (code === 0) {
44
+ // Try to extract URL from wrangler output
45
+ const urlMatch = outputData.match(/https:\/\/[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.workers\.dev/i) ||
46
+ outputData.match(/https:\/\/[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.pages\.dev/i) ||
47
+ outputData.match(/https:\/\/[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.cloudflare\.com/i) ||
48
+ outputData.match(/(https:\/\/[^\s"]+)/i);
49
+
50
+ let deployedUrl = null;
51
+ const lines = outputData.split('\n');
52
+ for (const line of lines) {
53
+ if (line.includes('workers.dev') || line.includes('pages.dev')) {
54
+ const match = line.match(/(https:\/\/[^\s"]+)/i);
55
+ if (match) deployedUrl = match[1];
56
+ }
57
+ }
58
+
59
+ // Fallback to the first URL we found if we didn't find a worker specific one
60
+ if (!deployedUrl && urlMatch) {
61
+ deployedUrl = urlMatch[1] || urlMatch[0];
62
+ }
63
+
64
+ if (deployedUrl) {
65
+ console.log(`\nāœ… Deployed successfully to: ${deployedUrl}`);
66
+
67
+ const localConfigPath = path.join(cwd, '.basebrick.config');
68
+ if (fs.existsSync(localConfigPath)) {
69
+ try {
70
+ const projectConfig = JSON.parse(fs.readFileSync(localConfigPath, 'utf8'));
71
+ projectConfig.url = deployedUrl;
72
+ projectConfig.environment = isPreview ? 'Preview' : 'Production';
73
+
74
+ fs.writeFileSync(localConfigPath, JSON.stringify(projectConfig, null, 2));
75
+ console.log('āœ… Added deployed URL to .basebrick.config');
76
+
77
+ // Automatically push the new config to the manager
78
+ console.log('\nšŸ”„ Automatically registering new URL with Bricklayer Manager...');
79
+ const manageModule = await import('./manage.js');
80
+ await manageModule.manageProject(cwd, {});
81
+ } catch(e) {
82
+ console.error('āŒ Failed to update .basebrick.config with URL:', e);
83
+ }
84
+ }
85
+ } else {
86
+ console.log('\nāœ… Deployed successfully (Could not extract URL automatically)');
87
+ }
88
+ } else {
89
+ console.error(`\nāŒ Deployment failed with exit code ${code}`);
90
+ process.exit(1);
91
+ }
92
+ });
93
+ }
package/bin/init.js CHANGED
@@ -35,6 +35,9 @@ export async function initProject(cwd) {
35
35
  const projectNameRaw = await question('What is the name of your project? (bricklayer-site) ');
36
36
  const projectName = projectNameRaw.trim() || 'bricklayer-site';
37
37
 
38
+ const projectDescriptionRaw = await question('Project description (optional): ');
39
+ const projectDescription = projectDescriptionRaw.trim() || '';
40
+
38
41
  const githubRepoRaw = await question('What is the GitHub repository URL? (leave blank for none) ');
39
42
  const githubRepo = githubRepoRaw.trim();
40
43
 
@@ -100,7 +103,7 @@ export async function initProject(cwd) {
100
103
  // 2. Create base CSS
101
104
  const cssPath = path.join(cwd, 'src/assets/tailwind/input.css');
102
105
  if (!fs.existsSync(cssPath)) {
103
- fs.writeFileSync(cssPath, `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`);
106
+ fs.writeFileSync(cssPath, `@import "tailwindcss";\n\n@source "../../**/*.njk";\n`);
104
107
  console.log(' Created src/assets/tailwind/input.css');
105
108
  }
106
109
 
@@ -209,11 +212,13 @@ url: post.title
209
212
  const pkgPath = path.join(cwd, 'package.json');
210
213
  let pkg = {
211
214
  name: projectName,
215
+ description: projectDescription,
212
216
  version: "1.0.0",
213
217
  type: "module",
214
218
  scripts: {
215
219
  build: "bricklayer",
216
- "build:prod": "bricklayer --prod"
220
+ "build:prod": "bricklayer --prod",
221
+ manage: "bricklayer manage"
217
222
  },
218
223
  dependencies: {},
219
224
  devDependencies: {
@@ -237,8 +242,8 @@ url: post.title
237
242
  if (includeCloudflare) {
238
243
  console.log('\nConfiguring Cloudflare...');
239
244
  pkg.scripts.start = "wrangler dev";
240
- pkg.scripts["deploy:preview"] = "npm run build:prod && wrangler deploy --env preview";
241
- pkg.scripts["deploy:prod"] = "npm run build:prod && wrangler deploy";
245
+ pkg.scripts["deploy:preview"] = "npm run build:prod && bricklayer deploy --preview";
246
+ pkg.scripts["deploy:prod"] = "npm run build:prod && bricklayer deploy";
242
247
  pkg.devDependencies.wrangler = "^3.0.0";
243
248
 
244
249
  const wranglerPath = path.join(cwd, 'wrangler.toml');
@@ -365,8 +370,9 @@ ${deploySteps}`;
365
370
 
366
371
  const basebrickConfigPath = path.join(cwd, '.basebrick.config');
367
372
  const basebrickConfig = {
368
- projectName,
369
- githubRepo,
373
+ name: projectName,
374
+ description: projectDescription,
375
+ githubUrl: githubRepo,
370
376
  includeDemo,
371
377
  includeSonic,
372
378
  pullSonic,
package/bin/manage.js CHANGED
@@ -10,7 +10,8 @@ const rl = readline.createInterface({
10
10
 
11
11
  const question = (query) => new Promise((resolve) => rl.question(query, resolve));
12
12
 
13
- export async function manageProject(cwd) {
13
+ export async function manageProject(cwd, options = {}) {
14
+ const { reconfigure = false, url = null, token = null } = options;
14
15
  const localConfigPath = path.join(cwd, '.basebrick.config');
15
16
 
16
17
  if (!fs.existsSync(localConfigPath)) {
@@ -30,9 +31,28 @@ export async function manageProject(cwd) {
30
31
  }
31
32
  }
32
33
 
34
+ if (reconfigure) {
35
+ managerConfig = {};
36
+ }
37
+
38
+ if (url) {
39
+ managerConfig.url = url;
40
+ }
41
+ if (token) {
42
+ managerConfig.token = token;
43
+ }
44
+
45
+ if (managerConfig.url && !managerConfig.url.endsWith('/api/sites')) {
46
+ managerConfig.url = managerConfig.url.replace(/\/+$/, '') + '/api/sites';
47
+ }
48
+
33
49
  if (!managerConfig.url) {
34
50
  const urlRaw = await question('What is your Bricklayer Manager URL? ');
35
- managerConfig.url = urlRaw.trim();
51
+ let url = urlRaw.trim();
52
+ if (!url.endsWith('/api/sites')) {
53
+ url = url.replace(/\/+$/, '') + '/api/sites';
54
+ }
55
+ managerConfig.url = url;
36
56
  }
37
57
 
38
58
  if (!managerConfig.token) {
@@ -58,6 +78,66 @@ export async function manageProject(cwd) {
58
78
  return;
59
79
  }
60
80
 
81
+ // Consolidate legacy keys to prefer user edits
82
+ if (projectConfig.projectName && projectConfig.projectName !== projectConfig.name) {
83
+ projectConfig.name = projectConfig.projectName;
84
+ }
85
+ if (projectConfig.projectDescription && projectConfig.projectDescription !== projectConfig.description) {
86
+ projectConfig.description = projectConfig.projectDescription;
87
+ }
88
+ if (projectConfig.githubRepo && projectConfig.githubRepo !== projectConfig.githubUrl) {
89
+ projectConfig.githubUrl = projectConfig.githubRepo;
90
+ }
91
+
92
+ // Clean up duplicate legacy keys
93
+ delete projectConfig.projectName;
94
+ delete projectConfig.projectDescription;
95
+ delete projectConfig.githubRepo;
96
+
97
+ // Normalize githubUrl
98
+ if (projectConfig.githubUrl) {
99
+ projectConfig.githubUrl = projectConfig.githubUrl.replace(/^git\+/, '').replace(/\.git$/, '');
100
+ }
101
+
102
+ // Extract description from package.json if not explicitly set
103
+ const pkgPath = path.join(cwd, 'package.json');
104
+ if (fs.existsSync(pkgPath)) {
105
+ try {
106
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
107
+ if (pkg.description && !projectConfig.description) {
108
+ projectConfig.description = pkg.description;
109
+ }
110
+ if (pkg.repository && pkg.repository.url && !projectConfig.githubUrl) {
111
+ let repoUrl = pkg.repository.url.replace(/^git\+/, '').replace(/\.git$/, '');
112
+ projectConfig.githubUrl = repoUrl;
113
+ }
114
+ } catch (e) {
115
+ console.error('Warning: Could not parse package.json');
116
+ }
117
+ }
118
+
119
+ // Extract name and account_id from wrangler.toml
120
+ const wranglerPath = path.join(cwd, 'wrangler.toml');
121
+ if (fs.existsSync(wranglerPath)) {
122
+ try {
123
+ const wranglerContent = fs.readFileSync(wranglerPath, 'utf8');
124
+ const nameMatch = wranglerContent.match(/^name\s*=\s*"([^"]+)"/m);
125
+ if (nameMatch && nameMatch[1] && !projectConfig.name) {
126
+ projectConfig.name = nameMatch[1];
127
+ }
128
+
129
+ const accountMatch = wranglerContent.match(/^account_id\s*=\s*"([^"]+)"/m);
130
+ if (accountMatch && accountMatch[1]) {
131
+ projectConfig.accountId = accountMatch[1];
132
+ }
133
+ } catch (e) {
134
+ console.error('Warning: Could not parse wrangler.toml');
135
+ }
136
+ }
137
+
138
+ // Save cleaned config back to .basebrick.config
139
+ fs.writeFileSync(localConfigPath, JSON.stringify(projectConfig, null, 2));
140
+
61
141
  console.log(`\nSending project configuration to ${managerConfig.url}...`);
62
142
 
63
143
  let endpoint = managerConfig.url;
package/history.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Version History
2
2
 
3
+ ## 1.2.0 (May 2026)
4
+
5
+ - **CLI Deployment Wrapper**: Introduced `bricklayer deploy` command using a pseudo-TTY to seamlessly wrap `wrangler deploy` across environments, automatically intercepting and capturing live Cloudflare URLs.
6
+ - **Automated Config Sync**: The `bricklayer manage` tool now seamlessly parses Cloudflare `account_id` from `wrangler.toml` and syncs it with your central configuration.
7
+ - **Manager Dashboard Upgrades**: Added real-time, silent dashboard polling and integrated direct Cloudflare Worker dashboard deep links for registered sites.
8
+ - **Site Management**: Implemented site deletion functionality within the manager UI and securely moved the API Configuration Token into an isolated Settings page.
9
+
3
10
  ## 1.1.0 (May 2026)
4
11
 
5
12
  - **Interactive Scaffolding**: Added `bricklayer init` command to scaffold new projects via an interactive wizard, generating `.gitignore` and `.basebrick.config` automatically.
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "basebrick-bricklayer",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "BaseBrick static site generator (JamBrick)",
6
6
  "main": "index.js",
7
7
  "bin": {
8
- "bricklayer": "./bin/bricklayer.js"
8
+ "bricklayer": "bin/bricklayer.js"
9
9
  },
10
10
  "keywords": [
11
11
  "jamstack",
@@ -21,4 +21,4 @@
21
21
  "gray-matter": "^4.0.3",
22
22
  "nunjucks": "^3.2.4"
23
23
  }
24
- }
24
+ }