generator-white-label 9.0.0 → 10.0.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/CLI.md +13 -17
- package/EXISTING_APPLICATIONS.md +155 -0
- package/PACKAGE_MANAGERS.md +6 -4
- package/README.md +135 -153
- package/app/404.ts +28 -0
- package/app/README.md +19 -7
- package/{scaffold/no-jsx/app → app}/assets/script/index.ts +1 -0
- package/{scaffold/no-jsx/app → app}/assets/script/tasks/TaskView.ts +1 -1
- package/app/assets/view/CodeBlock.ts +15 -0
- package/app/assets/view/examples/tasks/TaskExample.ts +50 -0
- package/app/assets/view/examples/tasks/task-state.ts +1 -1
- package/app/assets/view/layout/SiteFooter.ts +11 -0
- package/app/assets/view/layout/SiteHeader.ts +17 -0
- package/app/assets/view/page-data.ts +2 -2
- package/app/assets/view/sections/GettingStartedSection.ts +26 -0
- package/app/assets/view/sections/HeroSection.ts +15 -0
- package/app/assets/view/sections/LiveExampleSection.ts +24 -0
- package/app/assets/view/sections/PackageDocsSection.ts +69 -0
- package/app/assets/view/sections/{SourceGuideSection.tsx → SourceGuideSection.ts} +11 -16
- package/app/index.ts +50 -0
- package/cli/index.ts +4 -41
- package/dist/app/404.d.ts +1 -1
- package/dist/app/404.js +18 -2
- package/dist/app/404.js.map +1 -1
- package/dist/app/assets/script/index.d.ts +1 -6
- package/dist/app/assets/script/index.js +1 -6
- package/dist/app/assets/script/index.js.map +1 -1
- package/dist/app/assets/script/tasks/TaskView.d.ts +1 -6
- package/dist/app/assets/script/tasks/TaskView.js +2 -8
- package/dist/app/assets/script/tasks/TaskView.js.map +1 -1
- package/dist/app/assets/view/CodeBlock.d.ts +3 -3
- package/dist/app/assets/view/CodeBlock.js +4 -2
- package/dist/app/assets/view/CodeBlock.js.map +1 -1
- package/dist/app/assets/view/examples/tasks/TaskExample.d.ts +2 -8
- package/dist/app/assets/view/examples/tasks/TaskExample.js +41 -9
- package/dist/app/assets/view/examples/tasks/TaskExample.js.map +1 -1
- package/dist/app/assets/view/examples/tasks/task-state.d.ts +1 -1
- package/dist/app/assets/view/examples/tasks/task-state.js.map +1 -1
- package/dist/app/assets/view/layout/SiteFooter.d.ts +2 -2
- package/dist/app/assets/view/layout/SiteFooter.js +8 -3
- package/dist/app/assets/view/layout/SiteFooter.js.map +1 -1
- package/dist/app/assets/view/layout/SiteHeader.d.ts +2 -8
- package/dist/app/assets/view/layout/SiteHeader.js +14 -9
- package/dist/app/assets/view/layout/SiteHeader.js.map +1 -1
- package/dist/app/assets/view/page-data.d.ts +2 -2
- package/dist/app/assets/view/page-data.js.map +1 -1
- package/dist/app/assets/view/sections/GettingStartedSection.d.ts +1 -1
- package/dist/app/assets/view/sections/GettingStartedSection.js +21 -6
- package/dist/app/assets/view/sections/GettingStartedSection.js.map +1 -1
- package/dist/app/assets/view/sections/HeroSection.d.ts +2 -8
- package/dist/app/assets/view/sections/HeroSection.js +12 -9
- package/dist/app/assets/view/sections/HeroSection.js.map +1 -1
- package/dist/app/assets/view/sections/LiveExampleSection.d.ts +2 -6
- package/dist/app/assets/view/sections/LiveExampleSection.js +17 -7
- package/dist/app/assets/view/sections/LiveExampleSection.js.map +1 -1
- package/dist/app/assets/view/sections/PackageDocsSection.d.ts +1 -1
- package/dist/app/assets/view/sections/PackageDocsSection.js +57 -20
- package/dist/app/assets/view/sections/PackageDocsSection.js.map +1 -1
- package/dist/app/assets/view/sections/SourceGuideSection.d.ts +2 -7
- package/dist/app/assets/view/sections/SourceGuideSection.js +20 -8
- package/dist/app/assets/view/sections/SourceGuideSection.js.map +1 -1
- package/dist/app/index.d.ts +2 -8
- package/dist/app/index.js +36 -11
- package/dist/app/index.js.map +1 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.js +4 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/scaffold/index.d.ts +2 -13
- package/dist/scaffold/index.js +17 -53
- package/dist/scaffold/index.js.map +1 -1
- package/dist/scripts/build.js +97 -28
- package/dist/scripts/build.js.map +1 -1
- package/dist/server/handler.js +2 -9
- package/dist/server/handler.js.map +1 -1
- package/package.json +22 -22
- package/pnpm-workspace.yaml +0 -1
- package/scaffold/AGENTS.md +13 -8
- package/scaffold/index.ts +17 -57
- package/scripts/build.ts +90 -30
- package/server/handler.ts +2 -10
- package/tsconfig.site.json +1 -4
- package/app/404.tsx +0 -28
- package/app/assets/script/index.tsx +0 -13
- package/app/assets/script/tasks/TaskView.tsx +0 -24
- package/app/assets/view/CodeBlock.tsx +0 -17
- package/app/assets/view/examples/tasks/TaskExample.tsx +0 -63
- package/app/assets/view/layout/SiteFooter.tsx +0 -11
- package/app/assets/view/layout/SiteHeader.tsx +0 -23
- package/app/assets/view/sections/GettingStartedSection.tsx +0 -27
- package/app/assets/view/sections/HeroSection.tsx +0 -21
- package/app/assets/view/sections/LiveExampleSection.tsx +0 -29
- package/app/assets/view/sections/PackageDocsSection.tsx +0 -69
- package/app/index.tsx +0 -54
- package/scaffold/no-jsx/README.md +0 -73
- package/scaffold/no-jsx/app/404.ts +0 -10
- package/scaffold/no-jsx/app/assets/view/examples/tasks/TaskExample.ts +0 -39
- package/scaffold/no-jsx/app/index.ts +0 -58
- package/tsconfig.site.no-jsx.json +0 -24
package/dist/app/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { raw } from 'white-label-view/jsx-runtime';
|
|
1
|
+
import { html, unsafeHTML } from 'white-label-view/html';
|
|
3
2
|
import SiteFooter from './assets/view/layout/SiteFooter.js';
|
|
4
3
|
import SiteHeader from './assets/view/layout/SiteHeader.js';
|
|
5
4
|
import GettingStartedSection from './assets/view/sections/GettingStartedSection.js';
|
|
@@ -7,16 +6,42 @@ import HeroSection from './assets/view/sections/HeroSection.js';
|
|
|
7
6
|
import LiveExampleSection from './assets/view/sections/LiveExampleSection.js';
|
|
8
7
|
import PackageDocsSection from './assets/view/sections/PackageDocsSection.js';
|
|
9
8
|
import SourceGuideSection from './assets/view/sections/SourceGuideSection.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* kept at this boundary while individual views stay reusable and easy to read.
|
|
16
|
-
*/
|
|
9
|
+
function structuredDataScript(value) {
|
|
10
|
+
const json = JSON.stringify(value).replaceAll('<', '\\u003c');
|
|
11
|
+
return unsafeHTML(`<script type="application/ld+json">${json}</script>`);
|
|
12
|
+
}
|
|
13
|
+
/** Compose the static page from small tagged-template functions. */
|
|
17
14
|
export default function IndexPage(data) {
|
|
18
15
|
const page = data;
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
return html `<html id="index" lang="en-US" dir="ltr">
|
|
17
|
+
<head>
|
|
18
|
+
<meta charset="utf-8">
|
|
19
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
20
|
+
<title>${page.title}</title>
|
|
21
|
+
<meta name="description" content="${page.meta.description}">
|
|
22
|
+
<meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1">
|
|
23
|
+
<link rel="canonical" href="${page.siteUrl}/">
|
|
24
|
+
<meta property="og:title" content="${page.title}">
|
|
25
|
+
<meta property="og:description" content="${page.meta.description}">
|
|
26
|
+
<meta property="og:type" content="website">
|
|
27
|
+
<meta property="og:url" content="${page.siteUrl}/">
|
|
28
|
+
<link rel="stylesheet" href="${page.cdn}release/${page.version}/assets/style/global.css" media="all">
|
|
29
|
+
<link rel="stylesheet" href="${page.cdn}release/${page.version}/assets/style/print.css" media="print">
|
|
30
|
+
${structuredDataScript(page.structuredData)}
|
|
31
|
+
</head>
|
|
32
|
+
<body itemscope itemtype="https://schema.org/WebPage">
|
|
33
|
+
<a class="skip-link" href="#main">Skip to content</a>
|
|
34
|
+
${SiteHeader()}
|
|
35
|
+
<main id="main" tabindex="-1">
|
|
36
|
+
${HeroSection()}
|
|
37
|
+
${LiveExampleSection()}
|
|
38
|
+
${PackageDocsSection()}
|
|
39
|
+
${SourceGuideSection()}
|
|
40
|
+
${GettingStartedSection()}
|
|
41
|
+
</main>
|
|
42
|
+
${SiteFooter()}
|
|
43
|
+
<script src="${page.cdn}release/${page.version}/assets/script/index.compiled.js" defer></script>
|
|
44
|
+
</body>
|
|
45
|
+
</html>`;
|
|
21
46
|
}
|
|
22
47
|
//# sourceMappingURL=index.js.map
|
package/dist/app/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../app/index.
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../app/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,uBAAuB,CAAC;AACvD,OAAO,UAAU,MAAM,oCAAoC,CAAC;AAC5D,OAAO,UAAU,MAAM,oCAAoC,CAAC;AAE5D,OAAO,qBAAqB,MAAM,iDAAiD,CAAC;AACpF,OAAO,WAAW,MAAM,uCAAuC,CAAC;AAChE,OAAO,kBAAkB,MAAM,8CAA8C,CAAC;AAC9E,OAAO,kBAAkB,MAAM,8CAA8C,CAAC;AAC9E,OAAO,kBAAkB,MAAM,8CAA8C,CAAC;AAE9E,SAAS,oBAAoB,CAAC,KAAc;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC9D,OAAO,UAAU,CAAC,sCAAsC,IAAI,WAAW,CAAC,CAAC;AAC7E,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,IAA6B;IAC3D,MAAM,IAAI,GAAG,IAA2B,CAAC;IAEzC,OAAO,IAAI,CAAA;;;;yBAIU,IAAI,CAAC,KAAK;oDACiB,IAAI,CAAC,IAAI,CAAC,WAAW;;8CAE3B,IAAI,CAAC,OAAO;qDACL,IAAI,CAAC,KAAK;2DACJ,IAAI,CAAC,IAAI,CAAC,WAAW;;mDAE7B,IAAI,CAAC,OAAO;+CAChB,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,OAAO;+CAC/B,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,OAAO;kBAC5D,oBAAoB,CAAC,IAAI,CAAC,cAAc,CAAC;;;;kBAIzC,UAAU,EAAE;;sBAER,WAAW,EAAE;sBACb,kBAAkB,EAAE;sBACpB,kBAAkB,EAAE;sBACpB,kBAAkB,EAAE;sBACpB,qBAAqB,EAAE;;kBAE3B,UAAU,EAAE;+BACC,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,OAAO;;gBAE9C,CAAC;AACjB,CAAC","sourcesContent":["import {html, unsafeHTML} from 'white-label-view/html';\nimport SiteFooter from './assets/view/layout/SiteFooter.js';\nimport SiteHeader from './assets/view/layout/SiteHeader.js';\nimport type {PageData} from './assets/view/page-data.js';\nimport GettingStartedSection from './assets/view/sections/GettingStartedSection.js';\nimport HeroSection from './assets/view/sections/HeroSection.js';\nimport LiveExampleSection from './assets/view/sections/LiveExampleSection.js';\nimport PackageDocsSection from './assets/view/sections/PackageDocsSection.js';\nimport SourceGuideSection from './assets/view/sections/SourceGuideSection.js';\n\nfunction structuredDataScript(value: unknown) {\n const json = JSON.stringify(value).replaceAll('<', '\\\\u003c');\n return unsafeHTML(`<script type=\"application/ld+json\">${json}</script>`);\n}\n\n/** Compose the static page from small tagged-template functions. */\nexport default function IndexPage(data: Record<string, unknown>) {\n const page = data as unknown as PageData;\n\n return html`<html id=\"index\" lang=\"en-US\" dir=\"ltr\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>${page.title}</title>\n <meta name=\"description\" content=\"${page.meta.description}\">\n <meta name=\"robots\" content=\"index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1\">\n <link rel=\"canonical\" href=\"${page.siteUrl}/\">\n <meta property=\"og:title\" content=\"${page.title}\">\n <meta property=\"og:description\" content=\"${page.meta.description}\">\n <meta property=\"og:type\" content=\"website\">\n <meta property=\"og:url\" content=\"${page.siteUrl}/\">\n <link rel=\"stylesheet\" href=\"${page.cdn}release/${page.version}/assets/style/global.css\" media=\"all\">\n <link rel=\"stylesheet\" href=\"${page.cdn}release/${page.version}/assets/style/print.css\" media=\"print\">\n ${structuredDataScript(page.structuredData)}\n </head>\n <body itemscope itemtype=\"https://schema.org/WebPage\">\n <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n ${SiteHeader()}\n <main id=\"main\" tabindex=\"-1\">\n ${HeroSection()}\n ${LiveExampleSection()}\n ${PackageDocsSection()}\n ${SourceGuideSection()}\n ${GettingStartedSection()}\n </main>\n ${SiteFooter()}\n <script src=\"${page.cdn}release/${page.version}/assets/script/index.compiled.js\" defer></script>\n </body>\n </html>`;\n}\n"]}
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -5,9 +5,6 @@ export interface CliDependencies {
|
|
|
5
5
|
createProject?: typeof createProject;
|
|
6
6
|
stdout?: Pick<NodeJS.WriteStream, 'write'>;
|
|
7
7
|
stderr?: Pick<NodeJS.WriteStream, 'write'>;
|
|
8
|
-
input?: NodeJS.ReadableStream;
|
|
9
|
-
output?: NodeJS.WritableStream;
|
|
10
|
-
isInteractive?: boolean;
|
|
11
8
|
}
|
|
12
9
|
export declare function formatCliError(error: unknown): string;
|
|
13
10
|
export declare function runCli(args: readonly string[], dependencies?: CliDependencies): Promise<0 | 1>;
|
package/dist/cli/index.js
CHANGED
|
@@ -3,36 +3,23 @@ import { realpathSync } from 'node:fs';
|
|
|
3
3
|
import { readdir } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import process from 'node:process';
|
|
6
|
-
import { createInterface } from 'node:readline/promises';
|
|
7
6
|
import { fileURLToPath } from 'node:url';
|
|
8
7
|
import { createProject } from '../scaffold/index.js';
|
|
9
8
|
function usage() {
|
|
10
9
|
return [
|
|
11
10
|
'Usage:',
|
|
12
|
-
' white-label create <directory>
|
|
11
|
+
' white-label create <directory>',
|
|
13
12
|
'',
|
|
14
13
|
'Commands:',
|
|
15
|
-
' create <directory> Create a White Label project.',
|
|
14
|
+
' create <directory> Create a tagged-template White Label project.',
|
|
16
15
|
'',
|
|
17
16
|
'Options:',
|
|
18
|
-
' --jsx Generate TypeScript with JSX/TSX templates.',
|
|
19
|
-
' --no-jsx Generate plain TypeScript with HTML string templates.',
|
|
20
17
|
' -h, --help Show this help message.'
|
|
21
18
|
].join('\n');
|
|
22
19
|
}
|
|
23
20
|
export function formatCliError(error) {
|
|
24
21
|
return error instanceof Error ? error.message : String(error);
|
|
25
22
|
}
|
|
26
|
-
async function askForJsx(input, output) {
|
|
27
|
-
const readline = createInterface({ input, output });
|
|
28
|
-
try {
|
|
29
|
-
const answer = await readline.question('Use JSX/TSX for page and view templates? Choose Yes for JSX syntax like <section>...</section>, or No for plain TypeScript that returns HTML strings. (Y/n) ');
|
|
30
|
-
return !/^n(?:o)?$/i.test(answer.trim());
|
|
31
|
-
}
|
|
32
|
-
finally {
|
|
33
|
-
readline.close();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
23
|
/** Reject an existing non-empty target so a mistyped CLI destination cannot overwrite project files. */
|
|
37
24
|
async function assertDestinationAvailable(destination) {
|
|
38
25
|
try {
|
|
@@ -53,37 +40,18 @@ export async function runCli(args, dependencies = {}) {
|
|
|
53
40
|
const scaffold = dependencies.createProject ?? createProject;
|
|
54
41
|
const stdout = dependencies.stdout ?? process.stdout;
|
|
55
42
|
const stderr = dependencies.stderr ?? process.stderr;
|
|
56
|
-
const input = dependencies.input ?? process.stdin;
|
|
57
|
-
const output = dependencies.output ?? process.stdout;
|
|
58
43
|
const [command, destination, ...options] = args;
|
|
59
44
|
if (command === '--help' || command === '-h' || args.length === 0) {
|
|
60
45
|
stdout.write(`${usage()}\n`);
|
|
61
46
|
return 0;
|
|
62
47
|
}
|
|
63
|
-
|
|
64
|
-
if (command !== 'create' ||
|
|
65
|
-
!destination ||
|
|
66
|
-
options.some(option => !allowedOptions.has(option)) ||
|
|
67
|
-
(options.includes('--jsx') && options.includes('--no-jsx'))) {
|
|
48
|
+
if (command !== 'create' || !destination || options.length) {
|
|
68
49
|
stderr.write(`${usage()}\n`);
|
|
69
50
|
return 1;
|
|
70
51
|
}
|
|
71
|
-
let jsx;
|
|
72
|
-
if (options.includes('--jsx')) {
|
|
73
|
-
jsx = true;
|
|
74
|
-
}
|
|
75
|
-
else if (options.includes('--no-jsx')) {
|
|
76
|
-
jsx = false;
|
|
77
|
-
}
|
|
78
|
-
else if (dependencies.isInteractive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
79
|
-
jsx = await askForJsx(input, output);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
jsx = true;
|
|
83
|
-
}
|
|
84
52
|
const resolvedDestination = path.resolve(cwd(), destination);
|
|
85
53
|
await assertDestinationAvailable(resolvedDestination);
|
|
86
|
-
await scaffold({ destination: resolvedDestination
|
|
54
|
+
await scaffold({ destination: resolvedDestination });
|
|
87
55
|
stdout.write(`Created White Label project at ${resolvedDestination}\n`);
|
|
88
56
|
return 0;
|
|
89
57
|
}
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AACrC,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../cli/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport {realpathSync} from 'node:fs';\nimport {readdir} from 'node:fs/promises';\nimport path from 'node:path';\nimport process from 'node:process';\nimport {fileURLToPath} from 'node:url';\nimport {createProject} from '../scaffold/index.js';\n\nexport interface CliDependencies {\n cwd?: () => string;\n createProject?: typeof createProject;\n stdout?: Pick<NodeJS.WriteStream, 'write'>;\n stderr?: Pick<NodeJS.WriteStream, 'write'>;\n}\n\nfunction usage() {\n return [\n 'Usage:',\n ' white-label create <directory>',\n '',\n 'Commands:',\n ' create <directory> Create a tagged-template White Label project.',\n '',\n 'Options:',\n ' -h, --help Show this help message.'\n ].join('\\n');\n}\n\nexport function formatCliError(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Reject an existing non-empty target so a mistyped CLI destination cannot overwrite project files. */\nasync function assertDestinationAvailable(destination: string) {\n try {\n const entries = await readdir(destination);\n if (entries.length) {\n throw new Error(`Destination directory must be empty: ${destination}`);\n }\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {return;}\n throw error;\n }\n}\n\nexport async function runCli(args: readonly string[], dependencies: CliDependencies = {}) {\n const cwd = dependencies.cwd ?? process.cwd;\n const scaffold = dependencies.createProject ?? createProject;\n const stdout = dependencies.stdout ?? process.stdout;\n const stderr = dependencies.stderr ?? process.stderr;\n const [command, destination, ...options] = args;\n\n if (command === '--help' || command === '-h' || args.length === 0) {\n stdout.write(`${usage()}\\n`);\n return 0;\n }\n\n if (command !== 'create' || !destination || options.length) {\n stderr.write(`${usage()}\\n`);\n return 1;\n }\n\n const resolvedDestination = path.resolve(cwd(), destination);\n await assertDestinationAvailable(resolvedDestination);\n await scaffold({destination: resolvedDestination});\n stdout.write(`Created White Label project at ${resolvedDestination}\\n`);\n return 0;\n}\n\nconst invokedPath = path.resolve(process.argv[1] as string);\nconst modulePath = fileURLToPath(import.meta.url);\nif (realpathSync(invokedPath) === realpathSync(modulePath)) {\n runCli(process.argv.slice(2)).then(\n (exitCode) => {process.exitCode = exitCode;},\n (error: unknown) => {\n process.stderr.write(`Unable to create White Label project: ${formatCliError(error)}\\n`);\n process.exitCode = 1;\n }\n );\n}\n"]}
|
package/dist/scaffold/index.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ export interface ScaffoldFileSystem {
|
|
|
5
5
|
export interface CreateProjectOptions {
|
|
6
6
|
destination: string;
|
|
7
7
|
fileSystem?: ScaffoldFileSystem;
|
|
8
|
-
jsx?: boolean;
|
|
9
8
|
}
|
|
10
9
|
/** Return the package manifest used by generated White Label projects. */
|
|
11
10
|
export declare function createSiteManifest(): {
|
|
@@ -24,7 +23,6 @@ export declare function createSiteManifest(): {
|
|
|
24
23
|
allowScripts: {
|
|
25
24
|
'@parcel/watcher@2.5.1': boolean;
|
|
26
25
|
'esbuild@0.28.2': boolean;
|
|
27
|
-
'white-label-view@6.0.0': boolean;
|
|
28
26
|
};
|
|
29
27
|
dependenciesMeta: {
|
|
30
28
|
'@parcel/watcher@2.5.1': {
|
|
@@ -33,9 +31,6 @@ export declare function createSiteManifest(): {
|
|
|
33
31
|
'esbuild@0.28.2': {
|
|
34
32
|
built: boolean;
|
|
35
33
|
};
|
|
36
|
-
'white-label-view@6.0.0': {
|
|
37
|
-
built: boolean;
|
|
38
|
-
};
|
|
39
34
|
};
|
|
40
35
|
devDependencies: {
|
|
41
36
|
'@tailwindcss/cli': string;
|
|
@@ -54,11 +49,5 @@ export declare function createSiteManifest(): {
|
|
|
54
49
|
'white-label-view': string;
|
|
55
50
|
};
|
|
56
51
|
};
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
*
|
|
60
|
-
* This is the shared implementation behind every creation interface. Adapters
|
|
61
|
-
* translate their environment into these options instead of owning templates,
|
|
62
|
-
* overwrite policy, or project-generation behavior themselves.
|
|
63
|
-
*/
|
|
64
|
-
export declare function createProject({ destination, fileSystem, jsx }: CreateProjectOptions): Promise<void>;
|
|
52
|
+
/** Create the canonical tagged-template White Label starter without overwriting user files. */
|
|
53
|
+
export declare function createProject({ destination, fileSystem }: CreateProjectOptions): Promise<void>;
|
package/dist/scaffold/index.js
CHANGED
|
@@ -43,13 +43,11 @@ export function createSiteManifest() {
|
|
|
43
43
|
},
|
|
44
44
|
allowScripts: {
|
|
45
45
|
'@parcel/watcher@2.5.1': true,
|
|
46
|
-
'esbuild@0.28.2': true
|
|
47
|
-
'white-label-view@6.0.0': true
|
|
46
|
+
'esbuild@0.28.2': true
|
|
48
47
|
},
|
|
49
48
|
dependenciesMeta: {
|
|
50
49
|
'@parcel/watcher@2.5.1': { built: true },
|
|
51
|
-
'esbuild@0.28.2': { built: true }
|
|
52
|
-
'white-label-view@6.0.0': { built: true }
|
|
50
|
+
'esbuild@0.28.2': { built: true }
|
|
53
51
|
},
|
|
54
52
|
devDependencies: {
|
|
55
53
|
'@tailwindcss/cli': '4.3.3',
|
|
@@ -64,60 +62,26 @@ export function createSiteManifest() {
|
|
|
64
62
|
dependencies: {
|
|
65
63
|
'white-label-mediator': '5.0.0',
|
|
66
64
|
'white-label-model': '7.0.1',
|
|
67
|
-
'white-label-router': '6.
|
|
68
|
-
'white-label-view': '
|
|
65
|
+
'white-label-router': '6.1.0',
|
|
66
|
+
'white-label-view': '7.0.0'
|
|
69
67
|
}
|
|
70
68
|
};
|
|
71
69
|
}
|
|
72
|
-
const
|
|
73
|
-
['
|
|
74
|
-
['
|
|
75
|
-
['
|
|
76
|
-
['app
|
|
77
|
-
['app/
|
|
78
|
-
['
|
|
79
|
-
['
|
|
80
|
-
['
|
|
70
|
+
const scaffoldCopies = [
|
|
71
|
+
['.editorconfig', '.editorconfig'],
|
|
72
|
+
['.yarnrc.yml', '.yarnrc.yml'],
|
|
73
|
+
['pnpm-workspace.yaml', 'pnpm-workspace.yaml'],
|
|
74
|
+
['app', 'app'],
|
|
75
|
+
['app/README.md', 'README.md'],
|
|
76
|
+
['server', 'server'],
|
|
77
|
+
['scripts', 'scripts'],
|
|
78
|
+
['template-test', 'test'],
|
|
79
|
+
['tsconfig.site.json', 'tsconfig.json']
|
|
81
80
|
];
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
['scaffold/no-jsx/app/404.ts', 'app/404.ts'],
|
|
85
|
-
['scaffold/no-jsx/app/assets/script/index.ts', 'app/assets/script/index.ts'],
|
|
86
|
-
['scaffold/no-jsx/app/assets/script/tasks/TaskView.ts', 'app/assets/script/tasks/TaskView.ts'],
|
|
87
|
-
['scaffold/no-jsx/app/assets/view/examples/tasks/TaskExample.ts', 'app/assets/view/examples/tasks/TaskExample.ts'],
|
|
88
|
-
['scaffold/no-jsx/README.md', 'README.md'],
|
|
89
|
-
['tsconfig.site.no-jsx.json', 'tsconfig.json']
|
|
90
|
-
];
|
|
91
|
-
/**
|
|
92
|
-
* Create a White Label project without overwriting a non-empty destination.
|
|
93
|
-
*
|
|
94
|
-
* This is the shared implementation behind every creation interface. Adapters
|
|
95
|
-
* translate their environment into these options instead of owning templates,
|
|
96
|
-
* overwrite policy, or project-generation behavior themselves.
|
|
97
|
-
*/
|
|
98
|
-
export async function createProject({ destination, fileSystem = nodeFileSystem, jsx = true }) {
|
|
81
|
+
/** Create the canonical tagged-template White Label starter without overwriting user files. */
|
|
82
|
+
export async function createProject({ destination, fileSystem = nodeFileSystem }) {
|
|
99
83
|
await assertDestinationAvailable(destination);
|
|
100
|
-
const
|
|
101
|
-
['.editorconfig', '.editorconfig'],
|
|
102
|
-
['.yarnrc.yml', '.yarnrc.yml'],
|
|
103
|
-
['pnpm-workspace.yaml', 'pnpm-workspace.yaml'],
|
|
104
|
-
['app', 'app'],
|
|
105
|
-
['app/README.md', 'README.md'],
|
|
106
|
-
['server', 'server'],
|
|
107
|
-
['scripts', 'scripts'],
|
|
108
|
-
['template-test', 'test'],
|
|
109
|
-
['tsconfig.site.json', 'tsconfig.json']
|
|
110
|
-
] : [
|
|
111
|
-
['.editorconfig', '.editorconfig'],
|
|
112
|
-
['.yarnrc.yml', '.yarnrc.yml'],
|
|
113
|
-
['pnpm-workspace.yaml', 'pnpm-workspace.yaml'],
|
|
114
|
-
...commonNoJsxCopies,
|
|
115
|
-
...noJsxTemplateCopies,
|
|
116
|
-
['server', 'server'],
|
|
117
|
-
['scripts', 'scripts'],
|
|
118
|
-
['template-test', 'test']
|
|
119
|
-
];
|
|
120
|
-
for (const [source, target] of copies) {
|
|
84
|
+
for (const [source, target] of scaffoldCopies) {
|
|
121
85
|
await fileSystem.copy(path.join(packageRoot, source), path.join(destination, target));
|
|
122
86
|
}
|
|
123
87
|
await fileSystem.writeJSON(path.join(destination, 'package.json'), createSiteManifest());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../scaffold/index.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,OAAO,EAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAC/D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAC,aAAa,EAAC,MAAM,UAAU,CAAC;AAEvC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../scaffold/index.ts"],"names":[],"mappings":"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","sourcesContent":["/** @module scaffold */\nimport {cp, mkdir, readdir, writeFile} from 'node:fs/promises';\nimport path from 'node:path';\nimport {fileURLToPath} from 'node:url';\n\nconst packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');\n\nexport interface ScaffoldFileSystem {\n copy(source: string, destination: string): void | Promise<void>;\n writeJSON(destination: string, value: unknown): void | Promise<void>;\n}\n\nexport interface CreateProjectOptions {\n destination: string;\n fileSystem?: ScaffoldFileSystem;\n}\n\nconst nodeFileSystem: ScaffoldFileSystem = {\n async copy(source, destination) {\n await mkdir(path.dirname(destination), {recursive: true});\n await cp(source, destination, {recursive: true});\n },\n async writeJSON(destination, value) {\n await mkdir(path.dirname(destination), {recursive: true});\n await writeFile(destination, `${JSON.stringify(value, null, 2)}\\n`);\n }\n};\n\n/** Refuse to layer a generated project over existing user files. */\nasync function assertDestinationAvailable(destination: string) {\n try {\n const entries = await readdir(destination);\n if (entries.length) {throw new Error(`Destination directory must be empty: ${destination}`);}\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {return;}\n throw error;\n }\n}\n\n/** Return the package manifest used by generated White Label projects. */\nexport function createSiteManifest() {\n return {\n name: 'white-label-site',\n version: '1.0.0',\n private: true,\n type: 'module',\n engines: {node: '^22.18.0 || >=24.11.0'},\n scripts: {\n build: 'tsc -p tsconfig.json && node dist/scripts/build.js',\n typecheck: 'tsc -p tsconfig.json --noEmit',\n test: 'node --run typecheck && node --run build -- --version=test --production=true --site-url=https://example.com && node --test --experimental-test-coverage --test-coverage-include=dist/app/assets/script/index.js --test-coverage-lines=100 --test-coverage-functions=100 --test-coverage-branches=100 test/*.test.js'\n },\n allowScripts: {\n '@parcel/watcher@2.5.1': true,\n 'esbuild@0.28.2': true\n },\n dependenciesMeta: {\n '@parcel/watcher@2.5.1': {built: true},\n 'esbuild@0.28.2': {built: true}\n },\n devDependencies: {\n '@tailwindcss/cli': '4.3.3',\n '@types/node': '24.13.3',\n 'axe-core': '4.13.0',\n 'esbuild': '0.28.2',\n 'html-validate': '11.15.0',\n 'jsdom': '30.0.1',\n 'tailwindcss': '4.3.3',\n 'typescript': '7.0.2'\n },\n dependencies: {\n 'white-label-mediator': '5.0.0',\n 'white-label-model': '7.0.1',\n 'white-label-router': '6.1.0',\n 'white-label-view': '7.0.0'\n }\n };\n}\n\nconst scaffoldCopies = [\n ['.editorconfig', '.editorconfig'],\n ['.yarnrc.yml', '.yarnrc.yml'],\n ['pnpm-workspace.yaml', 'pnpm-workspace.yaml'],\n ['app', 'app'],\n ['app/README.md', 'README.md'],\n ['server', 'server'],\n ['scripts', 'scripts'],\n ['template-test', 'test'],\n ['tsconfig.site.json', 'tsconfig.json']\n] as const;\n\n/** Create the canonical tagged-template White Label starter without overwriting user files. */\nexport async function createProject({destination, fileSystem = nodeFileSystem}: CreateProjectOptions) {\n await assertDestinationAvailable(destination);\n for (const [source, target] of scaffoldCopies) {\n await fileSystem.copy(path.join(packageRoot, source), path.join(destination, target));\n }\n await fileSystem.writeJSON(path.join(destination, 'package.json'), createSiteManifest());\n}\n"]}
|
package/dist/scripts/build.js
CHANGED
|
@@ -6,27 +6,87 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
6
6
|
import { build as buildJavaScript } from 'esbuild';
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
8
8
|
const defaultProjectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
9
|
+
const relativePublicPrefix = /^\/(?!\/)[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$/;
|
|
10
|
+
function hasControlCharacters(value) {
|
|
11
|
+
return [...value].some(character => {
|
|
12
|
+
const code = character.charCodeAt(0);
|
|
13
|
+
return code <= 0x1f || code === 0x7f;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function normalizeVersion(value) {
|
|
17
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9._-]+$/.test(value) || value === '.' || value === '..') {
|
|
18
|
+
throw new Error('Version may contain only letters, numbers, dots, underscores, and hyphens and must name one release directory');
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function normalizeProduction(value) {
|
|
23
|
+
if (typeof value !== 'boolean') {
|
|
24
|
+
throw new Error('Production must be a boolean');
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function normalizeSiteUrl(value, production) {
|
|
29
|
+
if (typeof value !== 'string') {
|
|
30
|
+
throw new Error('Site URL must be an absolute HTTP(S) URL; production builds require HTTPS');
|
|
31
|
+
}
|
|
32
|
+
let url;
|
|
33
|
+
try {
|
|
34
|
+
url = new URL(value);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new Error('Site URL must be an absolute HTTP(S) URL; production builds require HTTPS');
|
|
38
|
+
}
|
|
39
|
+
if (hasControlCharacters(value) || (url.protocol !== 'http:' && url.protocol !== 'https:') ||
|
|
40
|
+
url.username || url.password || url.search || url.hash || (production && url.protocol !== 'https:')) {
|
|
41
|
+
throw new Error('Site URL must be an absolute HTTP(S) URL without credentials, query, or fragment; production builds require HTTPS');
|
|
42
|
+
}
|
|
43
|
+
return url.toString().replace(/\/$/, '');
|
|
44
|
+
}
|
|
45
|
+
function normalizePublicPrefix(value, name, production) {
|
|
46
|
+
if (typeof value !== 'string' || hasControlCharacters(value)) {
|
|
47
|
+
throw new Error(`${name} must be a root-relative or absolute HTTP(S) URL prefix`);
|
|
48
|
+
}
|
|
49
|
+
if (relativePublicPrefix.test(value)) {
|
|
50
|
+
return value.endsWith('/') ? value : `${value}/`;
|
|
51
|
+
}
|
|
52
|
+
let url;
|
|
53
|
+
try {
|
|
54
|
+
url = new URL(value);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new Error(`${name} must be a root-relative or absolute HTTP(S) URL prefix`);
|
|
58
|
+
}
|
|
59
|
+
if ((url.protocol !== 'http:' && url.protocol !== 'https:') || url.username || url.password ||
|
|
60
|
+
url.search || url.hash || (production && url.protocol !== 'https:')) {
|
|
61
|
+
throw new Error(`${name} must be a root-relative or absolute HTTP(S) URL prefix${production ? ' using HTTPS in production' : ''}`);
|
|
62
|
+
}
|
|
63
|
+
const normalized = url.toString();
|
|
64
|
+
return normalized.endsWith('/') ? normalized : `${normalized}/`;
|
|
65
|
+
}
|
|
66
|
+
function normalizeBuildConfig(config) {
|
|
67
|
+
const production = normalizeProduction(config.production);
|
|
68
|
+
return {
|
|
69
|
+
cdn: normalizePublicPrefix(config.cdn, 'cdn', production),
|
|
70
|
+
production,
|
|
71
|
+
siteUrl: normalizeSiteUrl(config.siteUrl, production),
|
|
72
|
+
version: normalizeVersion(config.version),
|
|
73
|
+
www: normalizePublicPrefix(config.www, 'www', production)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
9
76
|
/** Read deployment flags and reject unsafe or invalid deployment values. */
|
|
10
77
|
export function parseArguments(argumentsList) {
|
|
11
78
|
const values = Object.fromEntries(argumentsList.map((argument) => {
|
|
12
79
|
const [key, ...value] = argument.replace(/^--/, '').split('=');
|
|
13
80
|
return [key, value.join('=')];
|
|
14
81
|
}));
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
throw new Error('Version may contain only letters, numbers, dots, underscores, and hyphens');
|
|
18
|
-
}
|
|
19
|
-
const siteUrl = (values['site-url'] || 'http://localhost:8080').replace(/\/$/, '');
|
|
20
|
-
if (!URL.canParse(siteUrl) || (values.production === 'true' && !siteUrl.startsWith('https://'))) {
|
|
21
|
-
throw new Error('Production builds require an absolute HTTPS --site-url');
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
82
|
+
const production = values.production === 'true';
|
|
83
|
+
return normalizeBuildConfig({
|
|
24
84
|
cdn: values.cdn || '/',
|
|
25
|
-
production
|
|
26
|
-
siteUrl,
|
|
27
|
-
version,
|
|
85
|
+
production,
|
|
86
|
+
siteUrl: values['site-url'] || 'http://localhost:8080',
|
|
87
|
+
version: values.version || String(Math.floor(Date.now() / 1000)),
|
|
28
88
|
www: values.www || '/'
|
|
29
|
-
};
|
|
89
|
+
});
|
|
30
90
|
}
|
|
31
91
|
/** Return the local Tailwind executable name for the current platform. */
|
|
32
92
|
export function tailwindExecutable(platform = process.platform) {
|
|
@@ -41,15 +101,15 @@ async function filesUnder(directory) {
|
|
|
41
101
|
}));
|
|
42
102
|
return files.flat();
|
|
43
103
|
}
|
|
44
|
-
/** Render compiled TypeScript
|
|
104
|
+
/** Render compiled TypeScript pages using global, page-specific, and deployment data. */
|
|
45
105
|
async function renderMarkup(config, outputRoot, projectRoot) {
|
|
46
106
|
const globalData = JSON.parse(await fs.readFile(path.join(projectRoot, 'app/assets/data/view/global.json'), 'utf8'));
|
|
47
107
|
const appRoot = path.join(projectRoot, 'app');
|
|
48
108
|
const pages = (await filesUnder(appRoot))
|
|
49
|
-
.filter((file) => /\.
|
|
109
|
+
.filter((file) => /\.ts$/.test(file) && !file.endsWith('.d.ts') && !file.includes(`${path.sep}assets${path.sep}`));
|
|
50
110
|
await Promise.all(pages.map(async (page) => {
|
|
51
111
|
const relative = path.relative(appRoot, page);
|
|
52
|
-
const pageDataPath = path.join(projectRoot, 'app/assets/data/view', relative.replace(/\.
|
|
112
|
+
const pageDataPath = path.join(projectRoot, 'app/assets/data/view', relative.replace(/\.ts$/, '.json'));
|
|
53
113
|
let pageData = {};
|
|
54
114
|
try {
|
|
55
115
|
pageData = JSON.parse(await fs.readFile(pageDataPath, 'utf8'));
|
|
@@ -59,13 +119,13 @@ async function renderMarkup(config, outputRoot, projectRoot) {
|
|
|
59
119
|
throw error;
|
|
60
120
|
}
|
|
61
121
|
}
|
|
62
|
-
const compiledPage = path.join(projectRoot, 'dist/app', relative.replace(/\.
|
|
122
|
+
const compiledPage = path.join(projectRoot, 'dist/app', relative.replace(/\.ts$/, '.js'));
|
|
63
123
|
const module = await import(pathToFileURL(compiledPage).href);
|
|
64
124
|
if (typeof module.default !== 'function') {
|
|
65
125
|
throw new TypeError(`Page module ${relative} must export a default render function`);
|
|
66
126
|
}
|
|
67
127
|
const markup = module.default({ ...globalData, ...pageData, ...config });
|
|
68
|
-
const destination = path.join(outputRoot, relative.replace(/\.
|
|
128
|
+
const destination = path.join(outputRoot, relative.replace(/\.ts$/, '.html'));
|
|
69
129
|
await fs.mkdir(path.dirname(destination), { recursive: true });
|
|
70
130
|
await fs.writeFile(destination, `<!DOCTYPE html>\n${String(markup)}\n`);
|
|
71
131
|
}));
|
|
@@ -85,11 +145,11 @@ async function compileStyles(outputAssets, production, projectRoot) {
|
|
|
85
145
|
await execFileAsync(tailwindExecutable(), argumentsList, { cwd: projectRoot });
|
|
86
146
|
await fs.copyFile(path.join(styleRoot, 'print.css'), path.join(outputAssets, 'style/print.css'));
|
|
87
147
|
}
|
|
88
|
-
/** Bundle TypeScript entry points
|
|
148
|
+
/** Bundle plain TypeScript browser entry points, excluding declaration-only files. */
|
|
89
149
|
async function compileScripts(outputAssets, production, projectRoot) {
|
|
90
150
|
const scriptRoot = path.join(projectRoot, 'app/assets/script');
|
|
91
151
|
const entries = (await fs.readdir(scriptRoot, { withFileTypes: true }))
|
|
92
|
-
.filter((entry) => entry.isFile() && !entry.name.endsWith('.d.ts') &&
|
|
152
|
+
.filter((entry) => entry.isFile() && !entry.name.endsWith('.d.ts') && entry.name.endsWith('.ts'))
|
|
93
153
|
.map((entry) => path.join(scriptRoot, entry.name));
|
|
94
154
|
await fs.mkdir(path.join(outputAssets, 'script'), { recursive: true });
|
|
95
155
|
await buildJavaScript({
|
|
@@ -97,7 +157,7 @@ async function compileScripts(outputAssets, production, projectRoot) {
|
|
|
97
157
|
entryNames: '[name].compiled',
|
|
98
158
|
entryPoints: entries,
|
|
99
159
|
format: 'iife',
|
|
100
|
-
loader: { '.ts': 'ts'
|
|
160
|
+
loader: { '.ts': 'ts' },
|
|
101
161
|
minify: production,
|
|
102
162
|
outdir: path.join(outputAssets, 'script'),
|
|
103
163
|
sourcemap: !production,
|
|
@@ -105,20 +165,29 @@ async function compileScripts(outputAssets, production, projectRoot) {
|
|
|
105
165
|
});
|
|
106
166
|
await fs.writeFile(path.join(outputAssets, 'script/global.compiled.js'), '');
|
|
107
167
|
}
|
|
168
|
+
function escapeXml(value) {
|
|
169
|
+
return value
|
|
170
|
+
.replaceAll('&', '&')
|
|
171
|
+
.replaceAll('<', '<')
|
|
172
|
+
.replaceAll('>', '>')
|
|
173
|
+
.replaceAll('"', '"')
|
|
174
|
+
.replaceAll("'", ''');
|
|
175
|
+
}
|
|
108
176
|
/** Replace the deployment directory with compiled pages, scripts, styles, and static assets. */
|
|
109
177
|
export async function build(config = parseArguments(process.argv.slice(2)), projectRoot = defaultProjectRoot) {
|
|
178
|
+
const safeConfig = normalizeBuildConfig(config);
|
|
110
179
|
const outputRoot = path.join(projectRoot, '_deploy');
|
|
111
|
-
const outputAssets = path.join(outputRoot, 'release',
|
|
180
|
+
const outputAssets = path.join(outputRoot, 'release', safeConfig.version, 'assets');
|
|
112
181
|
await fs.rm(outputRoot, { recursive: true, force: true });
|
|
113
182
|
await fs.mkdir(outputAssets, { recursive: true });
|
|
114
183
|
await fs.cp(path.join(projectRoot, 'app/assets/data'), path.join(outputAssets, 'data'), { recursive: true });
|
|
115
|
-
await fs.writeFile(path.join(outputAssets, 'data/config.json'), JSON.stringify(
|
|
116
|
-
await renderMarkup(
|
|
117
|
-
await compileStyles(outputAssets,
|
|
118
|
-
await compileScripts(outputAssets,
|
|
119
|
-
const robots = `User-agent: *\nAllow: /\nSitemap: ${
|
|
184
|
+
await fs.writeFile(path.join(outputAssets, 'data/config.json'), JSON.stringify(safeConfig));
|
|
185
|
+
await renderMarkup(safeConfig, outputRoot, projectRoot);
|
|
186
|
+
await compileStyles(outputAssets, safeConfig.production, projectRoot);
|
|
187
|
+
await compileScripts(outputAssets, safeConfig.production, projectRoot);
|
|
188
|
+
const robots = `User-agent: *\nAllow: /\nSitemap: ${safeConfig.siteUrl}/sitemap.xml\n`;
|
|
120
189
|
await fs.writeFile(path.join(outputRoot, 'robots.txt'), robots);
|
|
121
|
-
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>${
|
|
190
|
+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>${escapeXml(`${safeConfig.siteUrl}/`)}</loc></url></urlset>\n`;
|
|
122
191
|
await fs.writeFile(path.join(outputRoot, 'sitemap.xml'), sitemap);
|
|
123
192
|
}
|
|
124
193
|
if (path.resolve(process.argv[1] || '') === fileURLToPath(import.meta.url)) {
|