create-berna-stencil 2.6.0 → 2.6.2
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 +1 -1
- package/_tools/assistant.js +92 -88
- package/_tools/modules/constants.js +23 -0
- package/_tools/modules/pageComponents.js +77 -0
- package/_tools/modules/updateData.js +25 -44
- package/_tools/modules/updateOutputPath.js +59 -63
- package/_tools/modules/updatePage.js +131 -104
- package/_tools/modules/utils.js +16 -19
- package/_tools/modules/validation.js +20 -0
- package/bin/create.js +74 -12
- package/out/.htaccess +16 -0
- package/out/404.html +163 -0
- package/out/assets/brand/favicon.svg +37 -0
- package/out/assets/brand/logo.svg +37 -0
- package/out/backend/.htaccess +7 -0
- package/out/backend/_core/composer.json +5 -0
- package/out/backend/_core/composer.lock +492 -0
- package/out/backend/_core/index.php +148 -0
- package/out/backend/_core/init.php +34 -0
- package/out/backend/_core/modules/RateLimiter.php +31 -0
- package/out/backend/_core/modules/Response.php +49 -0
- package/{src → out}/backend/_core/vendor/composer/autoload_static.php +10 -10
- package/{src → out}/backend/_core/vendor/composer/installed.json +6 -6
- package/{src → out}/backend/_core/vendor/composer/installed.php +2 -2
- package/out/backend/_core/vendor/graham-campbell/result-type/CHANGELOG.md +53 -0
- package/{src → out}/backend/_core/vendor/graham-campbell/result-type/LICENSE +21 -21
- package/out/backend/_core/vendor/graham-campbell/result-type/README.md +42 -0
- package/{src → out}/backend/_core/vendor/graham-campbell/result-type/composer.json +33 -33
- package/out/backend/_core/vendor/graham-campbell/result-type/phpunit.xml.dist +13 -0
- package/{src → out}/backend/_core/vendor/graham-campbell/result-type/src/Error.php +121 -121
- package/{src → out}/backend/_core/vendor/graham-campbell/result-type/src/Result.php +69 -69
- package/{src → out}/backend/_core/vendor/graham-campbell/result-type/src/Success.php +120 -120
- package/out/backend/_core/vendor/graham-campbell/result-type/tests/ResultTest.php +95 -0
- package/{src → out}/backend/_core/vendor/phpoption/phpoption/LICENSE +200 -200
- package/out/backend/_core/vendor/phpoption/phpoption/Makefile +17 -0
- package/out/backend/_core/vendor/phpoption/phpoption/README.md +201 -0
- package/{src → out}/backend/_core/vendor/phpoption/phpoption/composer.json +50 -50
- package/out/backend/_core/vendor/phpoption/phpoption/phpstan-baseline.neon +44 -0
- package/out/backend/_core/vendor/phpoption/phpoption/phpstan.neon.dist +7 -0
- package/out/backend/_core/vendor/phpoption/phpoption/phpunit.xml.dist +13 -0
- package/{src → out}/backend/_core/vendor/phpoption/phpoption/src/PhpOption/LazyOption.php +175 -175
- package/{src → out}/backend/_core/vendor/phpoption/phpoption/src/PhpOption/None.php +136 -136
- package/{src → out}/backend/_core/vendor/phpoption/phpoption/src/PhpOption/Option.php +434 -434
- package/{src → out}/backend/_core/vendor/phpoption/phpoption/src/PhpOption/Some.php +169 -169
- package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/EnsureTest.php +72 -0
- package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/LazyOptionTest.php +357 -0
- package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/NoneTest.php +153 -0
- package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/OptionTest.php +166 -0
- package/out/backend/_core/vendor/phpoption/phpoption/tests/PhpOption/Tests/SomeTest.php +194 -0
- package/out/backend/_core/vendor/phpoption/phpoption/tests/bootstrap.php +8 -0
- package/out/backend/_core/vendor/phpoption/phpoption/vendor-bin/phpstan/composer.json +8 -0
- package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/Ctype.php +232 -232
- package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/LICENSE +19 -19
- package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/README.md +12 -12
- package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/bootstrap.php +50 -50
- package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/bootstrap80.php +46 -46
- package/{src → out}/backend/_core/vendor/symfony/polyfill-ctype/composer.json +38 -38
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/LICENSE +19 -19
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Mbstring.php +1077 -1077
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/README.md +13 -13
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php +119 -119
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php +1397 -1397
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php +5 -5
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php +1489 -1489
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/bootstrap.php +171 -171
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/bootstrap80.php +167 -167
- package/{src → out}/backend/_core/vendor/symfony/polyfill-mbstring/composer.json +39 -39
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/LICENSE +19 -19
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Php80.php +115 -115
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/PhpToken.php +106 -106
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/README.md +25 -25
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php +31 -31
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php +16 -16
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php +20 -20
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php +16 -16
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php +16 -16
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/bootstrap.php +42 -42
- package/{src → out}/backend/_core/vendor/symfony/polyfill-php80/composer.json +37 -37
- package/out/backend/_core/vendor/vlucas/phpdotenv/.editorconfig +15 -0
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/LICENSE +30 -30
- package/out/backend/_core/vendor/vlucas/phpdotenv/Makefile +17 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/README.md +370 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/UPGRADING.md +196 -0
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/composer.json +60 -60
- package/out/backend/_core/vendor/vlucas/phpdotenv/phpstan-baseline.neon +157 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/phpstan.neon.dist +7 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/phpunit.xml.dist +13 -0
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Dotenv.php +267 -267
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.php +12 -12
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidEncodingException.php +12 -12
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidFileException.php +12 -12
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/InvalidPathException.php +12 -12
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Exception/ValidationException.php +12 -12
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Loader/Loader.php +48 -48
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Loader/LoaderInterface.php +20 -20
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Loader/Resolver.php +65 -65
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Entry.php +59 -59
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php +299 -299
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Lexer.php +58 -58
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Lines.php +127 -127
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Parser.php +53 -53
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/ParserInterface.php +19 -19
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Parser/Value.php +88 -88
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/AdapterInterface.php +15 -15
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ApacheAdapter.php +89 -89
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ArrayAdapter.php +80 -80
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php +88 -88
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/GuardedWriter.php +85 -85
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ImmutableWriter.php +110 -110
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiReader.php +48 -48
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiWriter.php +64 -64
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/PutenvAdapter.php +91 -91
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReaderInterface.php +17 -17
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReplacingWriter.php +104 -104
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php +88 -88
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/Adapter/WriterInterface.php +27 -27
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/AdapterRepository.php +107 -107
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/RepositoryBuilder.php +272 -272
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Repository/RepositoryInterface.php +51 -51
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/File/Paths.php +44 -44
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/File/Reader.php +81 -81
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/FileStore.php +72 -72
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/StoreBuilder.php +141 -141
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/StoreInterface.php +17 -17
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Store/StringStore.php +37 -37
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Util/Regex.php +112 -112
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Util/Str.php +108 -108
- package/{src → out}/backend/_core/vendor/vlucas/phpdotenv/src/Validator.php +207 -207
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/DotenvTest.php +387 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Loader/LoaderTest.php +86 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/EntryParserTest.php +234 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LexerTest.php +40 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LinesTest.php +53 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/ParserTest.php +98 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ArrayAdapterTest.php +57 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/EnvConstAdapterTest.php +75 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/PutenvAdapterTest.php +52 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ServerConstAdapterTest.php +75 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/RepositoryTest.php +305 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/Store/StoreTest.php +141 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/Dotenv/ValidatorTest.php +479 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/.env +5 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/assertions.env +18 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/booleans.env +33 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/commented.env +15 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/empty.env +1 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/example.env +1 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/exported.env +7 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/immutable.env +1 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/integers.env +17 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/large.env +2 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multibyte.env +3 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multiline.env +14 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/multiple.env +4 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/mutable.env +1 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/nested.env +15 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/quoted.env +11 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/specialchars.env +8 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/unicodevarnames.env +2 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/utf8-with-bom-encoding.env +3 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/tests/fixtures/env/windows.env +1 -0
- package/out/backend/_core/vendor/vlucas/phpdotenv/vendor-bin/phpstan/composer.json +15 -0
- package/out/backend/api/protected/example-protected.php +17 -0
- package/out/backend/api/public/example-public.php +17 -0
- package/out/backend/database/Database.php +24 -0
- package/out/backend/database/migrations/create_example_db.sql +1 -0
- package/out/backend/web.config +17 -0
- package/out/css/fonts/bootstrap-icons.woff +0 -0
- package/out/css/fonts/bootstrap-icons.woff2 +0 -0
- package/out/css/github-markdown-dark.css +1124 -0
- package/out/css/github-markdown-light.css +1124 -0
- package/out/css/pages/404.css +21517 -0
- package/out/css/pages/homepage.css +21508 -0
- package/out/data/site.json +44 -0
- package/out/index.html +1147 -0
- package/out/js/bootstrap.bundle.min.js +7 -0
- package/out/js/pages/js/pages/404.js +6 -0
- package/out/js/pages/js/pages/homepage.js +6 -0
- package/out/js/pages/ts/pages/404.js +6 -0
- package/out/js/pages/ts/pages/homepage.js +6 -0
- package/out/llms.txt +14 -0
- package/out/robots.txt +4 -0
- package/out/sitemap.xml +15 -0
- package/out/web.config +27 -0
- package/package.json +14 -47
- package/src/frontend/data/site.json +1 -1
- package/.gitignore +0 -4
- package/_tools/modules/updateIncludes.js +0 -81
- package/src/backend/config.example.php +0 -28
- /package/{src → out}/backend/_core/vendor/autoload.php +0 -0
- /package/{src → out}/backend/_core/vendor/composer/ClassLoader.php +0 -0
- /package/{src → out}/backend/_core/vendor/composer/InstalledVersions.php +0 -0
- /package/{src → out}/backend/_core/vendor/composer/LICENSE +0 -0
- /package/{src → out}/backend/_core/vendor/composer/autoload_classmap.php +0 -0
- /package/{src → out}/backend/_core/vendor/composer/autoload_files.php +0 -0
- /package/{src → out}/backend/_core/vendor/composer/autoload_namespaces.php +0 -0
- /package/{src → out}/backend/_core/vendor/composer/autoload_psr4.php +0 -0
- /package/{src → out}/backend/_core/vendor/composer/autoload_real.php +0 -0
- /package/{src → out}/backend/_core/vendor/composer/platform_check.php +0 -0
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Because it keeps you close to the fundamentals instead of hiding them, you spend
|
|
|
16
16
|
- 📁 **Scalable structure** — a clean, opinionated project layout that grows with your needs
|
|
17
17
|
- 🌍 **Open source** — free to use, free to modify, free to share
|
|
18
18
|
|
|
19
|
-

|
|
20
20
|

|
|
21
21
|

|
|
22
22
|
|
package/_tools/assistant.js
CHANGED
|
@@ -1,146 +1,150 @@
|
|
|
1
1
|
const readline = require('readline');
|
|
2
|
-
const { addPage, removePage, renamePage } = require('./modules/updatePage');
|
|
3
|
-
const { updateOutputPath, getCurrentOutputPath } = require('./modules/updateOutputPath');
|
|
4
2
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
const { addPage, removePage, renamePage, pageExists } = require('./modules/updatePage');
|
|
4
|
+
const { updateOutputPath, getCurrentOutputPath } = require('./modules/updateOutputPath');
|
|
5
|
+
const { validatePageName, validateOutputPath } = require('./modules/validation');
|
|
6
|
+
const { toKebabCase } = require('./modules/utils');
|
|
7
|
+
|
|
8
|
+
const color = {
|
|
9
|
+
reset: '\x1b[0m',
|
|
10
|
+
bold: '\x1b[1m',
|
|
11
|
+
dim: '\x1b[2m',
|
|
12
|
+
red: '\x1b[31m',
|
|
13
|
+
green: '\x1b[32m',
|
|
14
|
+
yellow: '\x1b[33m',
|
|
15
|
+
magenta: '\x1b[35m',
|
|
16
|
+
cyan: '\x1b[36m',
|
|
14
17
|
};
|
|
15
18
|
|
|
16
|
-
const
|
|
19
|
+
const rl = readline.createInterface({
|
|
17
20
|
input: process.stdin,
|
|
18
21
|
output: process.stdout,
|
|
19
|
-
terminal: true
|
|
22
|
+
terminal: true,
|
|
20
23
|
});
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
function toKebabCase(str) {
|
|
26
|
-
return str.trim().toLowerCase()
|
|
27
|
-
.replace(/[^a-z0-9\s_-]/g, '')
|
|
28
|
-
.replace(/[\s_]+/g, '-')
|
|
29
|
-
.replace(/-+/g, '-')
|
|
30
|
-
.replace(/^-+|-+$/g, '');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function validatePageName(name) {
|
|
34
|
-
if (!name) return 'Invalid name.';
|
|
35
|
-
if (name.length > MAX_NAME_LENGTH) return `Name must be ${MAX_NAME_LENGTH} characters or fewer.`;
|
|
36
|
-
if (!/^[a-z0-9-]+$/.test(name)) return 'Page name can only contain lowercase letters, numbers, and hyphens.';
|
|
37
|
-
if (/^\d/.test(name)) return 'Page name cannot start with a number.';
|
|
38
|
-
if (PROTECTED_PAGES.includes(name)) return `"${name}" is a protected page name.`;
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function validateOutputPath(input) {
|
|
43
|
-
if (!input.trim()) return 'Invalid path.';
|
|
44
|
-
if (input.includes('..')) return 'Path cannot contain "..".';
|
|
45
|
-
if (/[<>|?*"']/.test(input)) return 'Path contains invalid characters.';
|
|
46
|
-
return null;
|
|
25
|
+
function sanitizeInput(value) {
|
|
26
|
+
return (value ?? '').replace(/[\x00-\x1F\x7F]/g, '').trim();
|
|
47
27
|
}
|
|
48
28
|
|
|
49
|
-
function
|
|
50
|
-
return
|
|
29
|
+
function ask(prompt) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const onClose = () => resolve(null);
|
|
32
|
+
rl.once('close', onClose);
|
|
33
|
+
rl.question(prompt, (answer) => {
|
|
34
|
+
rl.off('close', onClose);
|
|
35
|
+
resolve(sanitizeInput(answer));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
51
38
|
}
|
|
52
39
|
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
40
|
+
async function confirm(prompt) {
|
|
41
|
+
const answer = await ask(`${prompt} ${color.dim}[y/N]${color.reset} `);
|
|
42
|
+
return /^y(es)?$/i.test((answer ?? '').trim());
|
|
57
43
|
}
|
|
58
44
|
|
|
59
45
|
async function askPageName(prompt) {
|
|
60
46
|
const raw = await ask(prompt);
|
|
47
|
+
if (raw === null) return null;
|
|
48
|
+
|
|
61
49
|
const name = toKebabCase(raw);
|
|
62
50
|
const error = validatePageName(name);
|
|
63
51
|
if (error) {
|
|
64
|
-
console.log(`\n${
|
|
52
|
+
console.log(`\n${color.red}✖ ${error}${color.reset}`);
|
|
65
53
|
return null;
|
|
66
54
|
}
|
|
67
55
|
return name;
|
|
68
56
|
}
|
|
69
57
|
|
|
70
|
-
async function
|
|
71
|
-
const name = await askPageName(`\n${
|
|
72
|
-
if (name) addPage(name
|
|
58
|
+
async function handleCreate() {
|
|
59
|
+
const name = await askPageName(`\n${color.green}❯${color.reset} Name of the new page: `);
|
|
60
|
+
if (name) addPage(name);
|
|
73
61
|
}
|
|
74
62
|
|
|
75
|
-
async function
|
|
76
|
-
const name = await askPageName(`\n${
|
|
77
|
-
if (name)
|
|
63
|
+
async function handleRemove() {
|
|
64
|
+
const name = await askPageName(`\n${color.red}❯${color.reset} Name of the page to remove: `);
|
|
65
|
+
if (!name) return;
|
|
66
|
+
|
|
67
|
+
if (!pageExists(name)) {
|
|
68
|
+
console.log(`\n${color.yellow}⚠ Page "${name}" does not exist.${color.reset}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const confirmed = await confirm(`This permanently deletes all files for "${name}".`);
|
|
73
|
+
if (!confirmed) {
|
|
74
|
+
console.log(`\n${color.dim}Cancelled.${color.reset}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
removePage(name);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
async function
|
|
81
|
-
const oldName = await askPageName(`\n${
|
|
80
|
+
async function handleRename() {
|
|
81
|
+
const oldName = await askPageName(`\n${color.yellow}❯${color.reset} Page to rename: `);
|
|
82
82
|
if (!oldName) return;
|
|
83
83
|
|
|
84
|
-
const newName = await askPageName(`${
|
|
84
|
+
const newName = await askPageName(`${color.yellow}❯${color.reset} New name: `);
|
|
85
85
|
if (!newName) return;
|
|
86
86
|
|
|
87
87
|
if (oldName === newName) {
|
|
88
|
-
console.log(`\n${
|
|
88
|
+
console.log(`\n${color.yellow}⚠ Old and new name are the same.${color.reset}`);
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
|
-
|
|
92
91
|
renamePage(oldName, newName);
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
async function
|
|
94
|
+
async function handleOutputPath() {
|
|
96
95
|
const current = getCurrentOutputPath();
|
|
97
|
-
const label
|
|
98
|
-
|
|
96
|
+
const label = current ? `\n${color.dim}Current path: "${current}"${color.reset}\n` : '\n';
|
|
97
|
+
|
|
98
|
+
const input = await ask(`${label}${color.magenta}❯${color.reset} New output path: `);
|
|
99
|
+
if (input === null) return;
|
|
99
100
|
|
|
100
101
|
const error = validateOutputPath(input);
|
|
101
102
|
if (error) {
|
|
102
|
-
console.log(`\n${
|
|
103
|
-
|
|
104
|
-
updateOutputPath(input);
|
|
103
|
+
console.log(`\n${color.red}✖ ${error}${color.reset}`);
|
|
104
|
+
return;
|
|
105
105
|
}
|
|
106
|
+
updateOutputPath(input);
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
const MENU_ACTIONS = {
|
|
109
|
-
'1':
|
|
110
|
-
'2':
|
|
111
|
-
'3':
|
|
112
|
-
'4':
|
|
110
|
+
'1': handleCreate,
|
|
111
|
+
'2': handleRemove,
|
|
112
|
+
'3': handleRename,
|
|
113
|
+
'4': handleOutputPath,
|
|
113
114
|
};
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
console.log(`\n${
|
|
116
|
+
function renderMenu() {
|
|
117
|
+
console.log(`\n${color.cyan}${color.bold}╭────────────────────────╮`);
|
|
117
118
|
console.log(`│ Berna-Stencil CLI │`);
|
|
118
|
-
console.log(`╰────────────────────────╯${
|
|
119
|
-
console.log(` ${
|
|
120
|
-
console.log(` ${
|
|
121
|
-
console.log(` ${
|
|
122
|
-
console.log(` ${
|
|
123
|
-
console.log(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
console.log(`╰────────────────────────╯${color.reset}\n`);
|
|
120
|
+
console.log(` ${color.green}1.${color.reset} Create page`);
|
|
121
|
+
console.log(` ${color.red}2.${color.reset} Remove page`);
|
|
122
|
+
console.log(` ${color.yellow}3.${color.reset} Rename page`);
|
|
123
|
+
console.log(` ${color.magenta}4.${color.reset} Configure output path`);
|
|
124
|
+
console.log(` ${color.dim}CTRL + C to exit\n`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function main() {
|
|
128
|
+
while (true) {
|
|
129
|
+
renderMenu();
|
|
130
|
+
|
|
131
|
+
const choice = await ask(`${color.cyan}❯${color.reset} Choose an option: `);
|
|
132
|
+
|
|
133
|
+
const action = MENU_ACTIONS[choice];
|
|
134
|
+
if (!action) {
|
|
135
|
+
console.log(`\n${color.red}✖ Invalid option.${color.reset}`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
131
138
|
|
|
132
|
-
const action = MENU_ACTIONS[choice];
|
|
133
|
-
if (action) {
|
|
134
139
|
try {
|
|
135
140
|
await action();
|
|
136
141
|
} catch (err) {
|
|
137
|
-
console.log(`\n${
|
|
142
|
+
console.log(`\n${color.red}✖ Unexpected error: ${err.message}${color.reset}`);
|
|
138
143
|
}
|
|
139
|
-
} else {
|
|
140
|
-
console.log(`\n${c.red}✖ Invalid option.${c.reset}`);
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
|
|
146
|
+
rl.close();
|
|
147
|
+
process.exit(0);
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
|
|
150
|
+
main();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
4
|
+
const FRONTEND_DIR = path.join(ROOT, 'src', 'frontend');
|
|
5
|
+
|
|
6
|
+
const MAX_PAGE_NAME_LENGTH = 50;
|
|
7
|
+
const PROTECTED_PAGES = Object.freeze(['homepage', '404']);
|
|
8
|
+
|
|
9
|
+
const PATHS = Object.freeze({
|
|
10
|
+
root: ROOT,
|
|
11
|
+
routes: path.join(FRONTEND_DIR, '_routes'),
|
|
12
|
+
scssPages: path.join(FRONTEND_DIR, 'scss', 'pages'),
|
|
13
|
+
jsPages: path.join(FRONTEND_DIR, 'js', 'pages'),
|
|
14
|
+
tsPages: path.join(FRONTEND_DIR, 'ts', 'pages'),
|
|
15
|
+
siteData: path.join(FRONTEND_DIR, 'data', 'site.json'),
|
|
16
|
+
pageComponents: path.join(FRONTEND_DIR, 'layouts', 'pageComponents.njk'),
|
|
17
|
+
templates: path.join(ROOT, '_tools', 'res', 'templates'),
|
|
18
|
+
eleventyConfig: path.join(ROOT, '.eleventy.js'),
|
|
19
|
+
packageJson: path.join(ROOT, 'package.json'),
|
|
20
|
+
tsconfig: path.join(ROOT, 'tsconfig.json'),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
module.exports = { PATHS, MAX_PAGE_NAME_LENGTH, PROTECTED_PAGES };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { PATHS } = require('./constants');
|
|
3
|
+
const { toCamelCase } = require('./utils');
|
|
4
|
+
|
|
5
|
+
function readPageComponents() {
|
|
6
|
+
if (!fs.existsSync(PATHS.pageComponents)) {
|
|
7
|
+
console.log(`[skip] not found: ${PATHS.pageComponents}`);
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return fs.readFileSync(PATHS.pageComponents, 'utf8');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function writePageComponents(content) {
|
|
14
|
+
fs.writeFileSync(PATHS.pageComponents, content);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function elifBlockRegex(camelName) {
|
|
18
|
+
return new RegExp(
|
|
19
|
+
`[ \\t]*\\{%\\s*elif\\s+title\\s*==\\s*"${camelName}"\\s*%\\}[\\s\\S]*?(?=[ \\t]*\\{%\\s*(?:elif|else|endif))`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function addPageBlock(pageName) {
|
|
24
|
+
const content = readPageComponents();
|
|
25
|
+
if (!content) return;
|
|
26
|
+
|
|
27
|
+
const camelName = toCamelCase(pageName);
|
|
28
|
+
if (content.includes(`{% elif title == "${camelName}" %}`)) return;
|
|
29
|
+
|
|
30
|
+
if (!content.includes('{% else %}')) {
|
|
31
|
+
console.log('[skip] no {% else %} anchor in pageComponents.njk');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const block =
|
|
36
|
+
`{% elif title == "${camelName}" %}\n` +
|
|
37
|
+
` {#{% include "component.njk" %}#}\n\n`;
|
|
38
|
+
|
|
39
|
+
writePageComponents(content.replace('{% else %}', `${block}{% else %}`));
|
|
40
|
+
console.log(`[updated] page block added for "${camelName}"`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function removePageBlock(pageName) {
|
|
44
|
+
const content = readPageComponents();
|
|
45
|
+
if (!content) return;
|
|
46
|
+
|
|
47
|
+
const camelName = toCamelCase(pageName);
|
|
48
|
+
if (!elifBlockRegex(camelName).test(content)) {
|
|
49
|
+
console.log(`[skip] page block for "${camelName}" not found`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const updated = content
|
|
54
|
+
.replace(elifBlockRegex(camelName), '')
|
|
55
|
+
.replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
56
|
+
|
|
57
|
+
writePageComponents(updated);
|
|
58
|
+
console.log(`[cleaned] page block removed for "${camelName}"`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renamePageBlock(oldName, newName) {
|
|
62
|
+
const content = readPageComponents();
|
|
63
|
+
if (!content) return;
|
|
64
|
+
|
|
65
|
+
const oldLine = `{% elif title == "${toCamelCase(oldName)}" %}`;
|
|
66
|
+
const newLine = `{% elif title == "${toCamelCase(newName)}" %}`;
|
|
67
|
+
|
|
68
|
+
if (!content.includes(oldLine)) {
|
|
69
|
+
console.log(`[skip] page block for "${toCamelCase(oldName)}" not found`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
writePageComponents(content.replace(oldLine, newLine));
|
|
74
|
+
console.log(`[renamed] page block "${toCamelCase(oldName)}" → "${toCamelCase(newName)}"`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { addPageBlock, removePageBlock, renamePageBlock };
|
|
@@ -1,72 +1,60 @@
|
|
|
1
|
-
const
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { PATHS } = require('./constants');
|
|
2
3
|
const { toCamelCase, toNiceTitle } = require('./utils');
|
|
3
4
|
|
|
4
|
-
const SITE_DATA_PATH = 'src/frontend/data/site.json';
|
|
5
|
-
|
|
6
|
-
// Returns the parsed site.json content, or null if the file doesn't exist
|
|
7
5
|
function readSiteData() {
|
|
8
|
-
if (!
|
|
9
|
-
|
|
6
|
+
if (!fs.existsSync(PATHS.siteData)) {
|
|
7
|
+
console.log(`[skip] not found: ${PATHS.siteData}`);
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const data = JSON.parse(fs.readFileSync(PATHS.siteData, 'utf8'));
|
|
12
|
+
if (!data.pages || typeof data.pages !== 'object') data.pages = {};
|
|
13
|
+
return data;
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.log(`[error] cannot parse site.json: ${err.message}`);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
10
18
|
}
|
|
11
19
|
|
|
12
|
-
// Serializes and writes the data object back to site.json
|
|
13
20
|
function writeSiteData(data) {
|
|
14
|
-
|
|
21
|
+
fs.writeFileSync(PATHS.siteData, `${JSON.stringify(data, null, 2)}\n`);
|
|
15
22
|
}
|
|
16
23
|
|
|
17
|
-
// --- Public API ---
|
|
18
|
-
|
|
19
|
-
// Adds a new page record to site.json
|
|
20
|
-
// Skips silently if the file doesn't exist or the record is already present
|
|
21
24
|
function addSiteData(pageName) {
|
|
22
25
|
const data = readSiteData();
|
|
23
26
|
if (!data) return;
|
|
24
27
|
|
|
25
28
|
const camelName = toCamelCase(pageName);
|
|
26
|
-
|
|
27
29
|
if (data.pages[camelName]) {
|
|
28
|
-
console.log(`[
|
|
30
|
+
console.log(`[skip] record "${camelName}" already exists`);
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
// Build the default page record with SEO metadata and empty CDN arrays
|
|
33
34
|
data.pages[camelName] = {
|
|
34
|
-
seo: {
|
|
35
|
-
|
|
36
|
-
description: 'description',
|
|
37
|
-
},
|
|
38
|
-
cdn: {
|
|
39
|
-
css: [],
|
|
40
|
-
js: []
|
|
41
|
-
}
|
|
35
|
+
seo: { title: toNiceTitle(pageName), description: 'description' },
|
|
36
|
+
cdn: { css: [], js: [] },
|
|
42
37
|
};
|
|
43
38
|
|
|
44
39
|
writeSiteData(data);
|
|
45
|
-
console.log(`[
|
|
40
|
+
console.log(`[updated] record "${camelName}" added`);
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
// Removes a page record from site.json
|
|
49
|
-
// Skips silently if the file doesn't exist or the record is not found
|
|
50
43
|
function removeSiteData(pageName) {
|
|
51
44
|
const data = readSiteData();
|
|
52
45
|
if (!data) return;
|
|
53
46
|
|
|
54
47
|
const camelName = toCamelCase(pageName);
|
|
55
|
-
|
|
56
48
|
if (!data.pages[camelName]) {
|
|
57
|
-
console.log(`[
|
|
49
|
+
console.log(`[skip] record "${camelName}" not found`);
|
|
58
50
|
return;
|
|
59
51
|
}
|
|
60
52
|
|
|
61
53
|
delete data.pages[camelName];
|
|
62
|
-
|
|
63
54
|
writeSiteData(data);
|
|
64
|
-
console.log(`[
|
|
55
|
+
console.log(`[cleaned] record "${camelName}" removed`);
|
|
65
56
|
}
|
|
66
57
|
|
|
67
|
-
// Renames a page record in site.json
|
|
68
|
-
// Preserves all existing fields (cdn, etc.) and only updates the SEO title
|
|
69
|
-
// Skips if the source record doesn't exist or the target name is already taken
|
|
70
58
|
function renameSiteData(oldName, newName) {
|
|
71
59
|
const data = readSiteData();
|
|
72
60
|
if (!data) return;
|
|
@@ -75,29 +63,22 @@ function renameSiteData(oldName, newName) {
|
|
|
75
63
|
const newCamel = toCamelCase(newName);
|
|
76
64
|
|
|
77
65
|
if (!data.pages[oldCamel]) {
|
|
78
|
-
console.log(`[
|
|
66
|
+
console.log(`[skip] record "${oldCamel}" not found`);
|
|
79
67
|
return;
|
|
80
68
|
}
|
|
81
|
-
|
|
82
69
|
if (data.pages[newCamel]) {
|
|
83
|
-
console.log(`[
|
|
70
|
+
console.log(`[skip] record "${newCamel}" already exists`);
|
|
84
71
|
return;
|
|
85
72
|
}
|
|
86
73
|
|
|
87
|
-
// Spread the existing record to preserve cdn and any future fields,
|
|
88
|
-
// then override only the seo.title with the new page name
|
|
89
74
|
data.pages[newCamel] = {
|
|
90
75
|
...data.pages[oldCamel],
|
|
91
|
-
seo: {
|
|
92
|
-
...data.pages[oldCamel].seo,
|
|
93
|
-
title: toNiceTitle(newName),
|
|
94
|
-
}
|
|
76
|
+
seo: { ...data.pages[oldCamel].seo, title: toNiceTitle(newName) },
|
|
95
77
|
};
|
|
96
|
-
|
|
97
78
|
delete data.pages[oldCamel];
|
|
98
79
|
|
|
99
80
|
writeSiteData(data);
|
|
100
|
-
console.log(`[
|
|
81
|
+
console.log(`[updated] record "${oldCamel}" renamed to "${newCamel}"`);
|
|
101
82
|
}
|
|
102
83
|
|
|
103
84
|
module.exports = { addSiteData, removeSiteData, renameSiteData };
|
|
@@ -1,113 +1,109 @@
|
|
|
1
|
-
const fs
|
|
1
|
+
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { PATHS } = require('./constants');
|
|
3
4
|
const { isTypeScriptProject } = require('./utils');
|
|
4
5
|
|
|
5
|
-
const ELEVENTY_CONFIG = path.resolve(__dirname, '../../.eleventy.js');
|
|
6
|
-
const PACKAGE_JSON = path.resolve(__dirname, '../../package.json');
|
|
7
|
-
const TSCONFIG = path.resolve(__dirname, '../../tsconfig.json');
|
|
8
|
-
|
|
9
6
|
const OUTPUT_DIR_REGEX = /const OUTPUT_DIR\s*=\s*['"`]([^'"`]*)['"`]/;
|
|
10
|
-
|
|
11
|
-
// --- Helpers ---
|
|
7
|
+
const TSCONFIG_OUTDIR_REGEX = /"outDir"\s*:\s*"[^"]*"/;
|
|
12
8
|
|
|
13
9
|
function parseOutputDir(content) {
|
|
14
10
|
const match = content.match(OUTPUT_DIR_REGEX);
|
|
15
11
|
return match ? match[1] : null;
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
// --- Updaters ---
|
|
19
|
-
|
|
20
14
|
function updateEleventyConfig(newPath) {
|
|
21
|
-
const content = fs.readFileSync(
|
|
22
|
-
|
|
23
|
-
const updated = content.replace(
|
|
24
|
-
OUTPUT_DIR_REGEX,
|
|
25
|
-
`const OUTPUT_DIR = "${newPath}"`
|
|
26
|
-
);
|
|
15
|
+
const content = fs.readFileSync(PATHS.eleventyConfig, 'utf8');
|
|
16
|
+
const updated = content.replace(OUTPUT_DIR_REGEX, `const OUTPUT_DIR = "${newPath}"`);
|
|
27
17
|
|
|
28
18
|
if (content === updated) {
|
|
29
|
-
console.log('
|
|
30
|
-
return
|
|
19
|
+
console.log('[skip] OUTPUT_DIR not found in .eleventy.js');
|
|
20
|
+
return;
|
|
31
21
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
console.log(`(✓) .eleventy.js updated → ${newPath}`);
|
|
35
|
-
return true;
|
|
22
|
+
fs.writeFileSync(PATHS.eleventyConfig, updated);
|
|
23
|
+
console.log(`[updated] .eleventy.js → ${newPath}`);
|
|
36
24
|
}
|
|
37
25
|
|
|
38
26
|
function updatePackageJson(newPath) {
|
|
39
|
-
const pkg = JSON.parse(fs.readFileSync(
|
|
27
|
+
const pkg = JSON.parse(fs.readFileSync(PATHS.packageJson, 'utf8'));
|
|
28
|
+
const scriptGlob = isTypeScriptProject()
|
|
29
|
+
? 'src/frontend/ts/pages/*.ts'
|
|
30
|
+
: 'src/frontend/js/pages/*.js';
|
|
40
31
|
|
|
41
32
|
pkg.outputDir = newPath;
|
|
42
|
-
|
|
43
|
-
const usesTs = isTypeScriptProject();
|
|
44
|
-
|
|
33
|
+
pkg.scripts = pkg.scripts || {};
|
|
45
34
|
pkg.scripts['build:css'] = `sass src/frontend/scss:${newPath}/css --no-source-map --style=compressed --quiet`;
|
|
46
35
|
pkg.scripts['serve:css'] = `sass --watch src/frontend/scss:${newPath}/css --no-source-map --quiet`;
|
|
36
|
+
pkg.scripts['build:js'] = `esbuild "${scriptGlob}" --bundle --outdir=${newPath}/js/pages --minify`;
|
|
37
|
+
pkg.scripts['serve:js'] = `esbuild "${scriptGlob}" --bundle --outdir=${newPath}/js/pages --watch`;
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
pkg.scripts['serve:js'] = `esbuild "src/frontend/ts/pages/*.ts" --bundle --outdir=${newPath}/js/pages --watch`;
|
|
51
|
-
} else {
|
|
52
|
-
pkg.scripts['build:js'] = `esbuild "src/frontend/js/pages/*.js" --bundle --outdir=${newPath}/js/pages --minify`;
|
|
53
|
-
pkg.scripts['serve:js'] = `esbuild "src/frontend/js/pages/*.js" --bundle --outdir=${newPath}/js/pages --watch`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
fs.writeFileSync(PACKAGE_JSON, JSON.stringify(pkg, null, 2), 'utf-8');
|
|
57
|
-
console.log(`(✓) package.json updated → ${newPath}`);
|
|
58
|
-
return true;
|
|
39
|
+
fs.writeFileSync(PATHS.packageJson, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
40
|
+
console.log(`[updated] package.json → ${newPath}`);
|
|
59
41
|
}
|
|
60
42
|
|
|
61
|
-
const TSCONFIG_OUTDIR_REGEX = /"outDir"\s*:\s*"[^"]*"/;
|
|
62
|
-
|
|
63
43
|
function updateTsConfig(newPath) {
|
|
64
44
|
if (!isTypeScriptProject()) return;
|
|
65
45
|
|
|
66
|
-
const content = fs.readFileSync(
|
|
46
|
+
const content = fs.readFileSync(PATHS.tsconfig, 'utf8');
|
|
67
47
|
const updated = content.replace(TSCONFIG_OUTDIR_REGEX, `"outDir": "./${newPath}/ts"`);
|
|
68
48
|
|
|
69
49
|
if (content === updated) {
|
|
70
|
-
console.log('
|
|
50
|
+
console.log('[skip] outDir not found in tsconfig.json');
|
|
71
51
|
return;
|
|
72
52
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
console.log(`(✓) tsconfig.json updated → ${newPath}/ts`);
|
|
53
|
+
fs.writeFileSync(PATHS.tsconfig, updated);
|
|
54
|
+
console.log(`[updated] tsconfig.json → ${newPath}/ts`);
|
|
76
55
|
}
|
|
77
56
|
|
|
78
|
-
|
|
57
|
+
function deleteOldOutput(oldPath) {
|
|
58
|
+
if (!oldPath) return;
|
|
79
59
|
|
|
80
|
-
|
|
81
|
-
const
|
|
60
|
+
const oldAbsPath = path.resolve(PATHS.root, oldPath);
|
|
61
|
+
const insideProject = oldAbsPath.startsWith(PATHS.root + path.sep);
|
|
62
|
+
|
|
63
|
+
if (!insideProject || oldAbsPath === PATHS.root) {
|
|
64
|
+
console.log(`[skip] refusing to delete "${oldAbsPath}" (outside project)`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (fs.existsSync(oldAbsPath)) {
|
|
68
|
+
fs.rmSync(oldAbsPath, { recursive: true, force: true });
|
|
69
|
+
console.log(`[deleted] ${oldAbsPath}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function updateOutputPath(rawPath) {
|
|
74
|
+
const trimmed = (rawPath ?? '').trim().replace(/\\/g, '/');
|
|
75
|
+
if (!trimmed) {
|
|
76
|
+
console.log('[skip] empty output path');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
82
79
|
|
|
83
80
|
const normalizedPath = trimmed === '.'
|
|
84
81
|
? 'out'
|
|
85
82
|
: `${trimmed.replace(/\/$/, '')}/${path.basename(process.cwd())}-out`;
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
fs.rmSync(oldAbsPath, { recursive: true, force: true });
|
|
94
|
-
console.log(`(✓) folder deleted → ${oldAbsPath}`);
|
|
95
|
-
} else {
|
|
96
|
-
console.log(`(i) folder not found, nothing to delete → ${oldAbsPath}`);
|
|
97
|
-
}
|
|
84
|
+
let oldPath = null;
|
|
85
|
+
try {
|
|
86
|
+
oldPath = parseOutputDir(fs.readFileSync(PATHS.eleventyConfig, 'utf8'));
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.log(`[error] cannot read .eleventy.js: ${err.message}`);
|
|
89
|
+
return;
|
|
98
90
|
}
|
|
99
91
|
|
|
100
|
-
|
|
92
|
+
deleteOldOutput(oldPath);
|
|
101
93
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
console.log(`\nupdating output path → "${normalizedPath}"`);
|
|
95
|
+
try {
|
|
96
|
+
updatePackageJson(normalizedPath);
|
|
97
|
+
updateEleventyConfig(normalizedPath);
|
|
98
|
+
updateTsConfig(normalizedPath);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.log(`[error] could not update output path: ${err.message}`);
|
|
101
|
+
}
|
|
105
102
|
}
|
|
106
103
|
|
|
107
104
|
function getCurrentOutputPath() {
|
|
108
105
|
try {
|
|
109
|
-
|
|
110
|
-
return parseOutputDir(content);
|
|
106
|
+
return parseOutputDir(fs.readFileSync(PATHS.eleventyConfig, 'utf8'));
|
|
111
107
|
} catch {
|
|
112
108
|
return null;
|
|
113
109
|
}
|