create-nextblock 0.1.0 → 0.2.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/bin/create-nextblock.js +1193 -920
- package/package.json +6 -2
- package/scripts/sync-template.js +279 -276
- package/templates/nextblock-template/.env.example +1 -14
- package/templates/nextblock-template/README.md +1 -1
- package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +67 -40
- package/templates/nextblock-template/app/[slug]/page.tsx +45 -10
- package/templates/nextblock-template/app/[slug]/page.utils.ts +92 -45
- package/templates/nextblock-template/app/api/revalidate/route.ts +15 -15
- package/templates/nextblock-template/app/{blog → article}/[slug]/PostClientContent.tsx +45 -43
- package/templates/nextblock-template/app/{blog → article}/[slug]/page.tsx +108 -98
- package/templates/nextblock-template/app/{blog → article}/[slug]/page.utils.ts +10 -3
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +25 -19
- package/templates/nextblock-template/app/cms/blocks/actions.ts +1 -1
- package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +1 -1
- package/templates/nextblock-template/app/cms/posts/actions.ts +47 -44
- package/templates/nextblock-template/app/cms/posts/page.tsx +2 -2
- package/templates/nextblock-template/app/cms/settings/languages/actions.ts +16 -15
- package/templates/nextblock-template/app/layout.tsx +9 -9
- package/templates/nextblock-template/app/lib/sitemap-utils.ts +52 -52
- package/templates/nextblock-template/app/sitemap.xml/route.ts +2 -2
- package/templates/nextblock-template/components/ResponsiveNav.tsx +22 -16
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -7
- package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +25 -26
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/proxy.ts +4 -4
- package/templates/nextblock-template/public/images/NBcover.webp +0 -0
- package/templates/nextblock-template/public/images/developer.webp +0 -0
- package/templates/nextblock-template/public/images/nextblock-logo-small.webp +0 -0
- package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
- package/templates/nextblock-template/public/images/programmer-upscaled.webp +0 -0
- package/templates/nextblock-template/scripts/backup.js +142 -47
- package/templates/nextblock-template/scripts/restore-working.js +102 -0
- package/templates/nextblock-template/scripts/restore.js +434 -0
- package/templates/nextblock-template/app/blog/page.tsx +0 -77
- package/templates/nextblock-template/backup/backup_2025-06-19.sql +0 -8057
- package/templates/nextblock-template/backup/backup_2025-06-20.sql +0 -8159
- package/templates/nextblock-template/backup/backup_2025-07-08.sql +0 -8411
- package/templates/nextblock-template/backup/backup_2025-07-09.sql +0 -8442
- package/templates/nextblock-template/backup/backup_2025-07-10.sql +0 -8442
- package/templates/nextblock-template/backup/backup_2025-10-01.sql +0 -8803
- package/templates/nextblock-template/backup/backup_2025-10-02.sql +0 -9749
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nextblock",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,10 +16,14 @@
|
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"type": "module",
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@clack/prompts": "^0.8.1",
|
|
19
20
|
"chalk": "^5.6.2",
|
|
20
21
|
"commander": "^14.0.1",
|
|
22
|
+
"execa": "^9.3.0",
|
|
21
23
|
"fs-extra": "^11.3.2",
|
|
22
24
|
"inquirer": "^12.10.0",
|
|
23
|
-
"
|
|
25
|
+
"open": "^10.1.0",
|
|
26
|
+
"ora": "^8.0.1",
|
|
27
|
+
"picocolors": "^1.1.1"
|
|
24
28
|
}
|
|
25
29
|
}
|
package/scripts/sync-template.js
CHANGED
|
@@ -1,276 +1,279 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { resolve, relative, sep, dirname } from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import fs from 'fs-extra';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = dirname(__filename);
|
|
10
|
-
const PROJECT_ROOT = resolve(__dirname, '..');
|
|
11
|
-
const SOURCE_DIR = resolve(PROJECT_ROOT, '../nextblock');
|
|
12
|
-
const TARGET_DIR = resolve(PROJECT_ROOT, 'templates/nextblock-template');
|
|
13
|
-
const REPO_ROOT = resolve(PROJECT_ROOT, '..', '..');
|
|
14
|
-
const UI_GLOBALS_SOURCE = resolve(PROJECT_ROOT, '../../libs/ui/src/styles/globals.css');
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
async function ensureTemplateSync() {
|
|
51
|
-
const sourceExists = await fs.pathExists(SOURCE_DIR);
|
|
52
|
-
if (!sourceExists) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Source project not found at ${SOURCE_DIR}. Please ensure apps/nextblock exists before syncing.`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
console.log(
|
|
59
|
-
chalk.blue(
|
|
60
|
-
`Syncing template from ${chalk.bold(relative(PROJECT_ROOT, SOURCE_DIR))} to ${chalk.bold(
|
|
61
|
-
relative(PROJECT_ROOT, TARGET_DIR),
|
|
62
|
-
)}`,
|
|
63
|
-
),
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
await fs.ensureDir(TARGET_DIR);
|
|
67
|
-
await fs.emptyDir(TARGET_DIR);
|
|
68
|
-
|
|
69
|
-
await fs.copy(SOURCE_DIR, TARGET_DIR, {
|
|
70
|
-
dereference: true,
|
|
71
|
-
filter: (src) => {
|
|
72
|
-
const rel = relative(SOURCE_DIR, src);
|
|
73
|
-
if (!rel) {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const segments = rel.split(sep);
|
|
78
|
-
return segments.every((segment) => !IGNORED_SEGMENTS.has(segment));
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
await ensureEnvExample();
|
|
83
|
-
await ensureGlobalStyles();
|
|
84
|
-
await ensureClientTranslations();
|
|
85
|
-
await sanitizeBlockEditorImports();
|
|
86
|
-
await sanitizeUiImports();
|
|
87
|
-
await ensureUiProxies();
|
|
88
|
-
await
|
|
89
|
-
await removeTemplateProjectJson();
|
|
90
|
-
|
|
91
|
-
console.log(chalk.green('Template sync complete.'));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function ensureEnvExample() {
|
|
95
|
-
const envTargets = [
|
|
96
|
-
resolve(REPO_ROOT, '.env.example'),
|
|
97
|
-
resolve(REPO_ROOT, '.env.exemple'),
|
|
98
|
-
resolve(SOURCE_DIR, '.env.example'),
|
|
99
|
-
resolve(SOURCE_DIR, '.env.exemple'),
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
const destination = resolve(TARGET_DIR, '.env.example');
|
|
103
|
-
|
|
104
|
-
for (const envPath of envTargets) {
|
|
105
|
-
if (await fs.pathExists(envPath)) {
|
|
106
|
-
await fs.copy(envPath, destination);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const placeholder = `# Environment variables for NextBlock CMS
|
|
112
|
-
NEXT_PUBLIC_SUPABASE_URL=
|
|
113
|
-
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
|
114
|
-
SUPABASE_SERVICE_ROLE_KEY=
|
|
115
|
-
SUPABASE_JWT_SECRET=
|
|
116
|
-
NEXT_PUBLIC_URL=http://localhost:3000
|
|
117
|
-
`;
|
|
118
|
-
|
|
119
|
-
await fs.writeFile(destination, placeholder);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function ensureGlobalStyles() {
|
|
123
|
-
const destination = resolve(TARGET_DIR, 'app/globals.css');
|
|
124
|
-
|
|
125
|
-
if (await fs.pathExists(destination)) {
|
|
126
|
-
await fs.remove(destination);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (await fs.pathExists(UI_GLOBALS_SOURCE)) {
|
|
130
|
-
await fs.copy(UI_GLOBALS_SOURCE, destination);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const fallback = `@tailwind base;
|
|
135
|
-
@tailwind components;
|
|
136
|
-
@tailwind utilities;
|
|
137
|
-
`;
|
|
138
|
-
|
|
139
|
-
await fs.outputFile(destination, fallback);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function ensureClientTranslations() {
|
|
143
|
-
const providersPath = resolve(TARGET_DIR, 'app/providers.tsx');
|
|
144
|
-
if (!(await fs.pathExists(providersPath))) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let content = await fs.readFile(providersPath, 'utf8');
|
|
149
|
-
const wrapperImportPath = '@nextblock-cms/utils';
|
|
150
|
-
const wrapperImportStatement = `import { TranslationsProvider } from '${wrapperImportPath}';`;
|
|
151
|
-
const existingImportRegex =
|
|
152
|
-
/import\s+\{\s*TranslationsProvider\s*\}\s*from\s*['"]@nextblock-cms\/utils['"];?/;
|
|
153
|
-
const legacyImportRegex =
|
|
154
|
-
/import\s+\{\s*TranslationsProvider\s*\}\s*from\s*['"]@\/lib\/client-translations['"];?/;
|
|
155
|
-
|
|
156
|
-
if (existingImportRegex.test(content) || legacyImportRegex.test(content)) {
|
|
157
|
-
content = content
|
|
158
|
-
.replace(existingImportRegex, wrapperImportStatement)
|
|
159
|
-
.replace(legacyImportRegex, wrapperImportStatement);
|
|
160
|
-
} else if (!content.includes(wrapperImportStatement)) {
|
|
161
|
-
const lines = content.split(/\r?\n/);
|
|
162
|
-
const insertIndex = lines.findIndex((line) => line.startsWith('import')) + 1;
|
|
163
|
-
if (insertIndex > 0) {
|
|
164
|
-
lines.splice(insertIndex, 0, wrapperImportStatement);
|
|
165
|
-
content = lines.join('\n');
|
|
166
|
-
} else {
|
|
167
|
-
content = `${wrapperImportStatement}\n${content}`;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
await fs.writeFile(providersPath, content);
|
|
172
|
-
|
|
173
|
-
const wrapperPath = resolve(TARGET_DIR, 'lib/client-translations.tsx');
|
|
174
|
-
if (await fs.pathExists(wrapperPath)) {
|
|
175
|
-
await fs.remove(wrapperPath);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async function sanitizeBlockEditorImports() {
|
|
180
|
-
const blockEditorPath = resolve(TARGET_DIR, 'app/cms/blocks/components/BlockEditorArea.tsx');
|
|
181
|
-
if (!(await fs.pathExists(blockEditorPath))) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const replacements = [
|
|
186
|
-
{ pattern: /(\.\.\/editors\/[A-Za-z0-9_-]+)\.js/g, replacement: '$1.tsx' },
|
|
187
|
-
{ pattern: /(\.\.\/actions)\.js/g, replacement: '$1.ts' },
|
|
188
|
-
];
|
|
189
|
-
|
|
190
|
-
const content = await fs.readFile(blockEditorPath, 'utf8');
|
|
191
|
-
let updated = content;
|
|
192
|
-
|
|
193
|
-
for (const { pattern, replacement } of replacements) {
|
|
194
|
-
updated = updated.replace(pattern, replacement);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (updated !== content) {
|
|
198
|
-
await fs.writeFile(blockEditorPath, updated);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function sanitizeUiImports() {
|
|
203
|
-
const searchDirs = ['app', 'components', 'context', 'lib'];
|
|
204
|
-
const validExtensions = new Set(['.ts', '.tsx', '.js', '.jsx']);
|
|
205
|
-
const filesToProcess = [];
|
|
206
|
-
|
|
207
|
-
for (const relativeDir of searchDirs) {
|
|
208
|
-
const absoluteDir = resolve(TARGET_DIR, relativeDir);
|
|
209
|
-
if (await fs.pathExists(absoluteDir)) {
|
|
210
|
-
await collectFiles(absoluteDir, filesToProcess, validExtensions);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
for (const filePath of filesToProcess) {
|
|
215
|
-
const original = await fs.readFile(filePath, 'utf8');
|
|
216
|
-
const replaced = original.replace(/@nextblock-cms\/ui\/(?!styles\/)[A-Za-z0-9/_-]+/g, '@nextblock-cms/ui');
|
|
217
|
-
|
|
218
|
-
if (replaced !== original) {
|
|
219
|
-
await fs.writeFile(filePath, replaced);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async function collectFiles(directory, accumulator, extensions) {
|
|
225
|
-
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
226
|
-
for (const entry of entries) {
|
|
227
|
-
const fullPath = resolve(directory, entry.name);
|
|
228
|
-
if (entry.isDirectory()) {
|
|
229
|
-
await collectFiles(fullPath, accumulator, extensions);
|
|
230
|
-
} else {
|
|
231
|
-
const dotIndex = entry.name.lastIndexOf('.');
|
|
232
|
-
if (dotIndex !== -1) {
|
|
233
|
-
const ext = entry.name.slice(dotIndex);
|
|
234
|
-
if (extensions.has(ext)) {
|
|
235
|
-
accumulator.push(fullPath);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function ensureUiProxies() {
|
|
243
|
-
const proxiesDir = resolve(TARGET_DIR, 'lib/ui');
|
|
244
|
-
await fs.ensureDir(proxiesDir);
|
|
245
|
-
|
|
246
|
-
const proxyContent = "export * from '@nextblock-cms/ui';\n";
|
|
247
|
-
|
|
248
|
-
for (const moduleName of UI_PROXY_MODULES) {
|
|
249
|
-
const proxyPath = resolve(proxiesDir, `${moduleName}.ts`);
|
|
250
|
-
if (!(await fs.pathExists(proxyPath))) {
|
|
251
|
-
await fs.outputFile(proxyPath, proxyContent);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
await
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { resolve, relative, sep, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const PROJECT_ROOT = resolve(__dirname, '..');
|
|
11
|
+
const SOURCE_DIR = resolve(PROJECT_ROOT, '../nextblock');
|
|
12
|
+
const TARGET_DIR = resolve(PROJECT_ROOT, 'templates/nextblock-template');
|
|
13
|
+
const REPO_ROOT = resolve(PROJECT_ROOT, '..', '..');
|
|
14
|
+
const UI_GLOBALS_SOURCE = resolve(PROJECT_ROOT, '../../libs/ui/src/styles/globals.css');
|
|
15
|
+
const UI_PROXY_MODULES = [
|
|
16
|
+
'avatar',
|
|
17
|
+
'badge',
|
|
18
|
+
'button',
|
|
19
|
+
'card',
|
|
20
|
+
'checkbox',
|
|
21
|
+
'ColorPicker',
|
|
22
|
+
'ConfirmationDialog',
|
|
23
|
+
'CustomSelectWithInput',
|
|
24
|
+
'dialog',
|
|
25
|
+
'dropdown-menu',
|
|
26
|
+
'input',
|
|
27
|
+
'label',
|
|
28
|
+
'popover',
|
|
29
|
+
'progress',
|
|
30
|
+
'select',
|
|
31
|
+
'separator',
|
|
32
|
+
'Skeleton',
|
|
33
|
+
'table',
|
|
34
|
+
'textarea',
|
|
35
|
+
'tooltip',
|
|
36
|
+
'ui',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const IGNORED_SEGMENTS = new Set([
|
|
40
|
+
'node_modules',
|
|
41
|
+
'.git',
|
|
42
|
+
'.next',
|
|
43
|
+
'dist',
|
|
44
|
+
'tmp',
|
|
45
|
+
'coverage',
|
|
46
|
+
'backup',
|
|
47
|
+
'backups',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
async function ensureTemplateSync() {
|
|
51
|
+
const sourceExists = await fs.pathExists(SOURCE_DIR);
|
|
52
|
+
if (!sourceExists) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Source project not found at ${SOURCE_DIR}. Please ensure apps/nextblock exists before syncing.`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(
|
|
59
|
+
chalk.blue(
|
|
60
|
+
`Syncing template from ${chalk.bold(relative(PROJECT_ROOT, SOURCE_DIR))} to ${chalk.bold(
|
|
61
|
+
relative(PROJECT_ROOT, TARGET_DIR),
|
|
62
|
+
)}`,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await fs.ensureDir(TARGET_DIR);
|
|
67
|
+
await fs.emptyDir(TARGET_DIR);
|
|
68
|
+
|
|
69
|
+
await fs.copy(SOURCE_DIR, TARGET_DIR, {
|
|
70
|
+
dereference: true,
|
|
71
|
+
filter: (src) => {
|
|
72
|
+
const rel = relative(SOURCE_DIR, src);
|
|
73
|
+
if (!rel) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const segments = rel.split(sep);
|
|
78
|
+
return segments.every((segment) => !IGNORED_SEGMENTS.has(segment));
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await ensureEnvExample();
|
|
83
|
+
await ensureGlobalStyles();
|
|
84
|
+
await ensureClientTranslations();
|
|
85
|
+
await sanitizeBlockEditorImports();
|
|
86
|
+
await sanitizeUiImports();
|
|
87
|
+
await ensureUiProxies();
|
|
88
|
+
await removeBackups();
|
|
89
|
+
await removeTemplateProjectJson();
|
|
90
|
+
|
|
91
|
+
console.log(chalk.green('Template sync complete.'));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function ensureEnvExample() {
|
|
95
|
+
const envTargets = [
|
|
96
|
+
resolve(REPO_ROOT, '.env.example'),
|
|
97
|
+
resolve(REPO_ROOT, '.env.exemple'),
|
|
98
|
+
resolve(SOURCE_DIR, '.env.example'),
|
|
99
|
+
resolve(SOURCE_DIR, '.env.exemple'),
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const destination = resolve(TARGET_DIR, '.env.example');
|
|
103
|
+
|
|
104
|
+
for (const envPath of envTargets) {
|
|
105
|
+
if (await fs.pathExists(envPath)) {
|
|
106
|
+
await fs.copy(envPath, destination);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const placeholder = `# Environment variables for NextBlock CMS
|
|
112
|
+
NEXT_PUBLIC_SUPABASE_URL=
|
|
113
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
|
114
|
+
SUPABASE_SERVICE_ROLE_KEY=
|
|
115
|
+
SUPABASE_JWT_SECRET=
|
|
116
|
+
NEXT_PUBLIC_URL=http://localhost:3000
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
await fs.writeFile(destination, placeholder);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function ensureGlobalStyles() {
|
|
123
|
+
const destination = resolve(TARGET_DIR, 'app/globals.css');
|
|
124
|
+
|
|
125
|
+
if (await fs.pathExists(destination)) {
|
|
126
|
+
await fs.remove(destination);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (await fs.pathExists(UI_GLOBALS_SOURCE)) {
|
|
130
|
+
await fs.copy(UI_GLOBALS_SOURCE, destination);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const fallback = `@tailwind base;
|
|
135
|
+
@tailwind components;
|
|
136
|
+
@tailwind utilities;
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
await fs.outputFile(destination, fallback);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function ensureClientTranslations() {
|
|
143
|
+
const providersPath = resolve(TARGET_DIR, 'app/providers.tsx');
|
|
144
|
+
if (!(await fs.pathExists(providersPath))) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let content = await fs.readFile(providersPath, 'utf8');
|
|
149
|
+
const wrapperImportPath = '@nextblock-cms/utils';
|
|
150
|
+
const wrapperImportStatement = `import { TranslationsProvider } from '${wrapperImportPath}';`;
|
|
151
|
+
const existingImportRegex =
|
|
152
|
+
/import\s+\{\s*TranslationsProvider\s*\}\s*from\s*['"]@nextblock-cms\/utils['"];?/;
|
|
153
|
+
const legacyImportRegex =
|
|
154
|
+
/import\s+\{\s*TranslationsProvider\s*\}\s*from\s*['"]@\/lib\/client-translations['"];?/;
|
|
155
|
+
|
|
156
|
+
if (existingImportRegex.test(content) || legacyImportRegex.test(content)) {
|
|
157
|
+
content = content
|
|
158
|
+
.replace(existingImportRegex, wrapperImportStatement)
|
|
159
|
+
.replace(legacyImportRegex, wrapperImportStatement);
|
|
160
|
+
} else if (!content.includes(wrapperImportStatement)) {
|
|
161
|
+
const lines = content.split(/\r?\n/);
|
|
162
|
+
const insertIndex = lines.findIndex((line) => line.startsWith('import')) + 1;
|
|
163
|
+
if (insertIndex > 0) {
|
|
164
|
+
lines.splice(insertIndex, 0, wrapperImportStatement);
|
|
165
|
+
content = lines.join('\n');
|
|
166
|
+
} else {
|
|
167
|
+
content = `${wrapperImportStatement}\n${content}`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await fs.writeFile(providersPath, content);
|
|
172
|
+
|
|
173
|
+
const wrapperPath = resolve(TARGET_DIR, 'lib/client-translations.tsx');
|
|
174
|
+
if (await fs.pathExists(wrapperPath)) {
|
|
175
|
+
await fs.remove(wrapperPath);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function sanitizeBlockEditorImports() {
|
|
180
|
+
const blockEditorPath = resolve(TARGET_DIR, 'app/cms/blocks/components/BlockEditorArea.tsx');
|
|
181
|
+
if (!(await fs.pathExists(blockEditorPath))) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const replacements = [
|
|
186
|
+
{ pattern: /(\.\.\/editors\/[A-Za-z0-9_-]+)\.js/g, replacement: '$1.tsx' },
|
|
187
|
+
{ pattern: /(\.\.\/actions)\.js/g, replacement: '$1.ts' },
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const content = await fs.readFile(blockEditorPath, 'utf8');
|
|
191
|
+
let updated = content;
|
|
192
|
+
|
|
193
|
+
for (const { pattern, replacement } of replacements) {
|
|
194
|
+
updated = updated.replace(pattern, replacement);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (updated !== content) {
|
|
198
|
+
await fs.writeFile(blockEditorPath, updated);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function sanitizeUiImports() {
|
|
203
|
+
const searchDirs = ['app', 'components', 'context', 'lib'];
|
|
204
|
+
const validExtensions = new Set(['.ts', '.tsx', '.js', '.jsx']);
|
|
205
|
+
const filesToProcess = [];
|
|
206
|
+
|
|
207
|
+
for (const relativeDir of searchDirs) {
|
|
208
|
+
const absoluteDir = resolve(TARGET_DIR, relativeDir);
|
|
209
|
+
if (await fs.pathExists(absoluteDir)) {
|
|
210
|
+
await collectFiles(absoluteDir, filesToProcess, validExtensions);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const filePath of filesToProcess) {
|
|
215
|
+
const original = await fs.readFile(filePath, 'utf8');
|
|
216
|
+
const replaced = original.replace(/@nextblock-cms\/ui\/(?!styles\/)[A-Za-z0-9/_-]+/g, '@nextblock-cms/ui');
|
|
217
|
+
|
|
218
|
+
if (replaced !== original) {
|
|
219
|
+
await fs.writeFile(filePath, replaced);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function collectFiles(directory, accumulator, extensions) {
|
|
225
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
const fullPath = resolve(directory, entry.name);
|
|
228
|
+
if (entry.isDirectory()) {
|
|
229
|
+
await collectFiles(fullPath, accumulator, extensions);
|
|
230
|
+
} else {
|
|
231
|
+
const dotIndex = entry.name.lastIndexOf('.');
|
|
232
|
+
if (dotIndex !== -1) {
|
|
233
|
+
const ext = entry.name.slice(dotIndex);
|
|
234
|
+
if (extensions.has(ext)) {
|
|
235
|
+
accumulator.push(fullPath);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function ensureUiProxies() {
|
|
243
|
+
const proxiesDir = resolve(TARGET_DIR, 'lib/ui');
|
|
244
|
+
await fs.ensureDir(proxiesDir);
|
|
245
|
+
|
|
246
|
+
const proxyContent = "export * from '@nextblock-cms/ui';\n";
|
|
247
|
+
|
|
248
|
+
for (const moduleName of UI_PROXY_MODULES) {
|
|
249
|
+
const proxyPath = resolve(proxiesDir, `${moduleName}.ts`);
|
|
250
|
+
if (!(await fs.pathExists(proxyPath))) {
|
|
251
|
+
await fs.outputFile(proxyPath, proxyContent);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function removeBackups() {
|
|
257
|
+
const targets = [
|
|
258
|
+
resolve(TARGET_DIR, 'backup'),
|
|
259
|
+
resolve(TARGET_DIR, 'backups'),
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
await Promise.all(
|
|
263
|
+
targets.map((dir) =>
|
|
264
|
+
fs
|
|
265
|
+
.remove(dir)
|
|
266
|
+
.catch(() => undefined),
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function removeTemplateProjectJson() {
|
|
272
|
+
const projectJsonPath = resolve(TARGET_DIR, 'project.json');
|
|
273
|
+
await fs.remove(projectJsonPath).catch(() => undefined);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
ensureTemplateSync().catch((error) => {
|
|
277
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
278
|
+
process.exit(1);
|
|
279
|
+
});
|
|
@@ -2,18 +2,9 @@ NEXT_PUBLIC_URL=
|
|
|
2
2
|
# Vercel / Supabase
|
|
3
3
|
SUPABASE_PROJECT_ID=
|
|
4
4
|
POSTGRES_URL=
|
|
5
|
-
POSTGRES_PRISMA_URL=
|
|
6
|
-
SUPABASE_URL=
|
|
7
5
|
NEXT_PUBLIC_SUPABASE_URL=
|
|
8
|
-
POSTGRES_URL_NON_POOLING=
|
|
9
|
-
SUPABASE_JWT_SECRET=
|
|
10
|
-
POSTGRES_USER=
|
|
11
6
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
|
12
|
-
POSTGRES_PASSWORD=
|
|
13
|
-
POSTGRES_DATABASE=
|
|
14
7
|
SUPABASE_SERVICE_ROLE_KEY=
|
|
15
|
-
POSTGRES_HOST=
|
|
16
|
-
SUPABASE_ANON_KEY=
|
|
17
8
|
|
|
18
9
|
# Cloudflare
|
|
19
10
|
NEXT_PUBLIC_R2_BASE_URL=
|
|
@@ -21,12 +12,8 @@ R2_ACCESS_KEY_ID=
|
|
|
21
12
|
R2_SECRET_ACCESS_KEY=
|
|
22
13
|
R2_BUCKET_NAME=
|
|
23
14
|
R2_ACCOUNT_ID=
|
|
24
|
-
NEXT_PUBLIC_R2_PUBLIC_URL=
|
|
25
|
-
R2_TOKEN_VALUE=
|
|
26
15
|
|
|
27
|
-
NEXTJS_REVALIDATE_URL=
|
|
28
16
|
REVALIDATE_SECRET_TOKEN=
|
|
29
|
-
LHCI_GITHUB_APP_TOKEN=
|
|
30
17
|
|
|
31
18
|
# Email SMTP Configuration
|
|
32
19
|
SMTP_HOST=
|
|
@@ -34,4 +21,4 @@ SMTP_PORT=
|
|
|
34
21
|
SMTP_USER=
|
|
35
22
|
SMTP_PASS=
|
|
36
23
|
SMTP_FROM_EMAIL=
|
|
37
|
-
SMTP_FROM_NAME=
|
|
24
|
+
SMTP_FROM_NAME=
|
|
@@ -146,7 +146,7 @@ It features:
|
|
|
146
146
|
* `app/cms/[entity]/`: CRUD pages for different content types (pages, posts, media, users, navigation, languages).
|
|
147
147
|
* `app/cms/blocks/`: Components and actions related to the block editor.
|
|
148
148
|
* `app/[slug]/`: Dynamic route for public "Pages".
|
|
149
|
-
* `app/
|
|
149
|
+
* `app/article/[slug]/`: Dynamic route for public "Articles".
|
|
150
150
|
* `app/api/`: API routes (e.g., for revalidation, R2 pre-signed URLs).
|
|
151
151
|
* `components/`: Shared UI components (shadcn/ui based).
|
|
152
152
|
* `components/ui/`: shadcn/ui components.
|