@blinkk/root 2.0.0-rc.0 → 2.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/dist/{chunk-5YDJSEDK.js → chunk-3KGEENDW.js} +47 -1
- package/dist/chunk-3KGEENDW.js.map +1 -0
- package/dist/{chunk-7D26JHIF.js → chunk-A4DNVP34.js} +335 -140
- package/dist/chunk-A4DNVP34.js.map +1 -0
- package/dist/{chunk-UJ6F5RQ5.js → chunk-HTBDTUFE.js} +148 -63
- package/dist/chunk-HTBDTUFE.js.map +1 -0
- package/dist/{chunk-ZUUGSC6Z.js → chunk-MA6MSEP4.js} +1 -1
- package/dist/chunk-MA6MSEP4.js.map +1 -0
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +4 -4
- package/dist/core.d.ts +35 -5
- package/dist/core.js +82 -9
- package/dist/core.js.map +1 -1
- package/dist/functions.js +4 -4
- package/dist/middleware.d.ts +2 -2
- package/dist/node.d.ts +17 -3
- package/dist/node.js +6 -2
- package/dist/render.d.ts +1 -1
- package/dist/render.js +12741 -125
- package/dist/render.js.map +1 -1
- package/dist/{types-RwR6rV2O.d.ts → types-Mr1zFAT1.d.ts} +80 -28
- package/package.json +6 -3
- package/dist/chunk-5YDJSEDK.js.map +0 -1
- package/dist/chunk-7D26JHIF.js.map +0 -1
- package/dist/chunk-UJ6F5RQ5.js.map +0 -1
- package/dist/chunk-ZUUGSC6Z.js.map +0 -1
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
dirExists,
|
|
13
13
|
directoryContains,
|
|
14
14
|
fileExists,
|
|
15
|
+
flattenPackageDepsFromMonorepo,
|
|
15
16
|
isDirectory,
|
|
16
17
|
isJsFile,
|
|
17
18
|
loadBundledConfig,
|
|
@@ -21,21 +22,21 @@ import {
|
|
|
21
22
|
rmDir,
|
|
22
23
|
writeFile,
|
|
23
24
|
writeJson
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-HTBDTUFE.js";
|
|
25
26
|
import {
|
|
26
27
|
configureServerPlugins,
|
|
27
28
|
getVitePlugins
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-MA6MSEP4.js";
|
|
29
30
|
import {
|
|
30
31
|
RouteTrie,
|
|
31
32
|
htmlMinify,
|
|
32
33
|
htmlPretty,
|
|
33
34
|
isValidTagName,
|
|
34
35
|
parseTagNames
|
|
35
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-3KGEENDW.js";
|
|
36
37
|
|
|
37
38
|
// src/cli/cli.ts
|
|
38
|
-
import { Command } from "commander";
|
|
39
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
39
40
|
import { bgGreen, black } from "kleur/colors";
|
|
40
41
|
|
|
41
42
|
// src/cli/build.ts
|
|
@@ -369,11 +370,6 @@ async function build(rootProjectDir, options) {
|
|
|
369
370
|
});
|
|
370
371
|
}
|
|
371
372
|
const rootPlugins = rootConfig.plugins || [];
|
|
372
|
-
for (const plugin of rootPlugins) {
|
|
373
|
-
if (typeof plugin.hooks?.startup === "function") {
|
|
374
|
-
await plugin.hooks.startup({ command: "build", rootConfig });
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
373
|
const viteConfig = rootConfig.vite || {};
|
|
378
374
|
const vitePlugins = [
|
|
379
375
|
...viteConfig.plugins || [],
|
|
@@ -605,7 +601,17 @@ async function build(rootProjectDir, options) {
|
|
|
605
601
|
if (!ssrOnly) {
|
|
606
602
|
const render = await import(path3.join(distDir, "server/render.js"));
|
|
607
603
|
const renderer = new render.Renderer(rootConfig, { assetMap, elementGraph });
|
|
608
|
-
|
|
604
|
+
let sitemap = await renderer.getSitemap();
|
|
605
|
+
if (options?.filter) {
|
|
606
|
+
const filterRegex = new RegExp(`^${options.filter}`);
|
|
607
|
+
const filteredSitemap = {};
|
|
608
|
+
Object.entries(sitemap).forEach(([urlPath, sitemapItem]) => {
|
|
609
|
+
if (urlPath.match(filterRegex)) {
|
|
610
|
+
filteredSitemap[urlPath] = sitemapItem;
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
sitemap = filteredSitemap;
|
|
614
|
+
}
|
|
609
615
|
const sitemapXmlItems = [];
|
|
610
616
|
if (rootConfig.sitemap && !rootConfig.domain) {
|
|
611
617
|
throw new Error(
|
|
@@ -615,56 +621,94 @@ async function build(rootProjectDir, options) {
|
|
|
615
621
|
const domain = rootConfig.domain;
|
|
616
622
|
console.log("\nhtml output:");
|
|
617
623
|
const batchSize = Number(options?.concurrency || 10);
|
|
618
|
-
await batchAsyncCalls(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
let outFilePath = path3.join(urlPath.slice(1), "index.html");
|
|
628
|
-
if (outFilePath.endsWith("404/index.html")) {
|
|
629
|
-
outFilePath = outFilePath.replace("404/index.html", "404.html");
|
|
624
|
+
await batchAsyncCalls(
|
|
625
|
+
Object.entries(sitemap),
|
|
626
|
+
batchSize,
|
|
627
|
+
async ([urlPath, sitemapItem]) => {
|
|
628
|
+
if (rootConfig.build?.excludeDefaultLocaleFromIntlPaths) {
|
|
629
|
+
const defaultLocale = rootConfig.i18n?.defaultLocale || "en";
|
|
630
|
+
if (sitemapItem.locale === defaultLocale) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
630
633
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (sitemapItem.alts) {
|
|
640
|
-
Object.entries(sitemapItem.alts).forEach(([altLocale, item]) => {
|
|
641
|
-
sitemapXmlItem.alts.push({
|
|
642
|
-
url: `${domain}${item.urlPath}`,
|
|
643
|
-
locale: altLocale,
|
|
644
|
-
hreflang: item.hrefLang
|
|
634
|
+
try {
|
|
635
|
+
const routeModule = sitemapItem.route.module;
|
|
636
|
+
if (routeModule.getStaticContent) {
|
|
637
|
+
let props;
|
|
638
|
+
if (routeModule.getStaticProps) {
|
|
639
|
+
props = await routeModule.getStaticProps({
|
|
640
|
+
rootConfig,
|
|
641
|
+
params: sitemapItem.params
|
|
645
642
|
});
|
|
646
|
-
|
|
643
|
+
if (props?.notFound) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
} else {
|
|
647
|
+
props = { rootConfig, params: sitemapItem.params };
|
|
648
|
+
}
|
|
649
|
+
const result = await routeModule.getStaticContent(props);
|
|
650
|
+
let body;
|
|
651
|
+
if (typeof result === "string") {
|
|
652
|
+
body = result;
|
|
653
|
+
} else if (result && typeof result === "object") {
|
|
654
|
+
body = result.body;
|
|
655
|
+
} else {
|
|
656
|
+
body = "";
|
|
657
|
+
}
|
|
658
|
+
const outFilePath2 = urlPath.slice(1);
|
|
659
|
+
const outPath2 = path3.join(buildDir, outFilePath2);
|
|
660
|
+
await makeDir(path3.dirname(outPath2));
|
|
661
|
+
await writeFile(outPath2, body);
|
|
662
|
+
printFileOutput(fileSize(outPath2), "dist/html/", outFilePath2);
|
|
663
|
+
return;
|
|
647
664
|
}
|
|
665
|
+
const data = await renderer.renderRoute(sitemapItem.route, {
|
|
666
|
+
routeParams: sitemapItem.params
|
|
667
|
+
});
|
|
668
|
+
if (data.notFound) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
let outFilePath = path3.join(urlPath.slice(1), "index.html");
|
|
672
|
+
if (outFilePath.endsWith("404/index.html")) {
|
|
673
|
+
outFilePath = outFilePath.replace("404/index.html", "404.html");
|
|
674
|
+
}
|
|
675
|
+
const outPath = path3.join(buildDir, outFilePath);
|
|
676
|
+
if (rootConfig.sitemap && outFilePath.endsWith("index.html")) {
|
|
677
|
+
const sitemapXmlItem = {
|
|
678
|
+
url: `${domain}${urlPath}`,
|
|
679
|
+
locale: sitemapItem.locale,
|
|
680
|
+
alts: []
|
|
681
|
+
};
|
|
682
|
+
sitemapXmlItems.push(sitemapXmlItem);
|
|
683
|
+
if (sitemapItem.alts) {
|
|
684
|
+
Object.entries(sitemapItem.alts).forEach(([altLocale, item]) => {
|
|
685
|
+
sitemapXmlItem.alts.push({
|
|
686
|
+
url: `${domain}${item.urlPath}`,
|
|
687
|
+
locale: altLocale,
|
|
688
|
+
hreflang: item.hrefLang
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
let html = data.html || "";
|
|
694
|
+
if (rootConfig.prettyHtml) {
|
|
695
|
+
html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
|
|
696
|
+
} else if (rootConfig.minifyHtml) {
|
|
697
|
+
html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
|
|
698
|
+
}
|
|
699
|
+
await writeFile(outPath, html);
|
|
700
|
+
printFileOutput(fileSize(outPath), "dist/html/", outFilePath);
|
|
701
|
+
} catch (e) {
|
|
702
|
+
logBuildError(
|
|
703
|
+
{ route: sitemapItem.route, params: sitemapItem.params, urlPath },
|
|
704
|
+
e
|
|
705
|
+
);
|
|
706
|
+
throw new Error(
|
|
707
|
+
`BuildError: ${urlPath} (${sitemapItem.route.src}) failed to build.`
|
|
708
|
+
);
|
|
648
709
|
}
|
|
649
|
-
let html = data.html || "";
|
|
650
|
-
if (rootConfig.prettyHtml !== false) {
|
|
651
|
-
html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
|
|
652
|
-
}
|
|
653
|
-
if (rootConfig.minifyHtml !== false) {
|
|
654
|
-
html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
|
|
655
|
-
}
|
|
656
|
-
await writeFile(outPath, html);
|
|
657
|
-
printFileOutput(fileSize(outPath), "dist/html/", outFilePath);
|
|
658
|
-
} catch (e) {
|
|
659
|
-
logBuildError(
|
|
660
|
-
{ route: sitemapItem.route, params: sitemapItem.params, urlPath },
|
|
661
|
-
e
|
|
662
|
-
);
|
|
663
|
-
throw new Error(
|
|
664
|
-
`BuildError: ${urlPath} (${sitemapItem.route.src}) failed to build.`
|
|
665
|
-
);
|
|
666
710
|
}
|
|
667
|
-
|
|
711
|
+
);
|
|
668
712
|
if (rootConfig.sitemap) {
|
|
669
713
|
const sitemapXmlBuilder = [];
|
|
670
714
|
sitemapXmlItems.sort((a, b) => a.url.localeCompare(b.url));
|
|
@@ -860,7 +904,8 @@ async function createPackage(rootProjectDir, options) {
|
|
|
860
904
|
}
|
|
861
905
|
}
|
|
862
906
|
if (target === "appengine") {
|
|
863
|
-
|
|
907
|
+
const appYamlPath = options?.appYaml || path5.join(rootDir, "app.yaml");
|
|
908
|
+
await onAppEngine({ packageJson, outDir, appYamlPath });
|
|
864
909
|
} else if (target === "firebase") {
|
|
865
910
|
await onFirebase({ rootDir, packageJson, outDir });
|
|
866
911
|
}
|
|
@@ -883,77 +928,30 @@ async function generatePackageJson(rootDir) {
|
|
|
883
928
|
const packageJson = await loadJson(
|
|
884
929
|
path5.resolve(rootDir, "package.json")
|
|
885
930
|
);
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
async function updatePeerDepsFromWorkspace(rootDir, packageJson) {
|
|
892
|
-
const requiredPeerDeps = getRequiredPeerDeps(packageJson);
|
|
893
|
-
if (requiredPeerDeps.length === 0) {
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
const workspaceRoot = await findWorkspaceRoot(rootDir);
|
|
897
|
-
if (!workspaceRoot) {
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
const workspacePackageJsonPath = path5.resolve(workspaceRoot, "package.json");
|
|
901
|
-
const workspacePackageJson = await loadJson(
|
|
902
|
-
workspacePackageJsonPath
|
|
903
|
-
);
|
|
904
|
-
const workspaceDeps = workspacePackageJson.dependencies || {};
|
|
905
|
-
function setDep(name, value) {
|
|
906
|
-
if (!packageJson.dependencies) {
|
|
907
|
-
packageJson.dependencies = {};
|
|
931
|
+
const allDeps = flattenPackageDepsFromMonorepo(rootDir);
|
|
932
|
+
packageJson.dependencies ??= {};
|
|
933
|
+
for (const depName in allDeps) {
|
|
934
|
+
if (!packageJson.dependencies[depName]) {
|
|
935
|
+
packageJson.dependencies[depName] = allDeps[depName];
|
|
908
936
|
}
|
|
909
|
-
packageJson.dependencies[name] = value;
|
|
910
|
-
}
|
|
911
|
-
for (const peerDep of requiredPeerDeps) {
|
|
912
|
-
const workspaceDepVersion = workspaceDeps[peerDep];
|
|
913
|
-
if (workspaceDepVersion) {
|
|
914
|
-
setDep(peerDep, workspaceDepVersion);
|
|
915
|
-
} else {
|
|
916
|
-
console.warn(
|
|
917
|
-
`could not find peer dep "${peerDep}" in workspace "${workspacePackageJsonPath}"`
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
async function findWorkspaceRoot(rootDir) {
|
|
923
|
-
const parentDir = path5.dirname(rootDir);
|
|
924
|
-
if (parentDir === "/") {
|
|
925
|
-
return null;
|
|
926
937
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
}
|
|
931
|
-
const packageJsonPath = path5.resolve(parentDir, "package.json");
|
|
932
|
-
if (await fileExists(packageJsonPath)) {
|
|
933
|
-
const parentPackageJson = await loadJson(packageJsonPath);
|
|
934
|
-
if (parentPackageJson && parentPackageJson.workspaces) {
|
|
935
|
-
return parentDir;
|
|
938
|
+
Object.entries(packageJson.dependencies).forEach(([depName, depVersion]) => {
|
|
939
|
+
if (depVersion.startsWith("workspace:")) {
|
|
940
|
+
delete packageJson.dependencies[depName];
|
|
936
941
|
}
|
|
942
|
+
});
|
|
943
|
+
if (packageJson.peerDependencies) {
|
|
944
|
+
delete packageJson.peerDependencies;
|
|
937
945
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
function getRequiredPeerDeps(packageJson) {
|
|
941
|
-
const requiredPeerDeps = [];
|
|
942
|
-
const peerDeps = packageJson.peerDependencies || {};
|
|
943
|
-
const peerDepsMeta = packageJson.peerDependenciesMeta || {};
|
|
944
|
-
for (const key in peerDeps) {
|
|
945
|
-
const meta = peerDepsMeta[key];
|
|
946
|
-
if (!meta?.optional) {
|
|
947
|
-
requiredPeerDeps.push(key);
|
|
948
|
-
}
|
|
946
|
+
if (packageJson.devDependencies) {
|
|
947
|
+
delete packageJson.devDependencies;
|
|
949
948
|
}
|
|
950
|
-
return
|
|
949
|
+
return packageJson;
|
|
951
950
|
}
|
|
952
951
|
async function onAppEngine(options) {
|
|
953
|
-
const {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
await fs4.copyFile(configPath, path5.resolve(outDir, "app.yaml"));
|
|
952
|
+
const { outDir, packageJson, appYamlPath } = options;
|
|
953
|
+
if (await fileExists(appYamlPath)) {
|
|
954
|
+
await fs4.copyFile(appYamlPath, path5.resolve(outDir, "app.yaml"));
|
|
957
955
|
}
|
|
958
956
|
if (packageJson.scripts?.start) {
|
|
959
957
|
packageJson.scripts = { start: packageJson.scripts.start };
|
|
@@ -1061,9 +1059,15 @@ function verifyRedirectConfig(redirect) {
|
|
|
1061
1059
|
if (!redirect.source) {
|
|
1062
1060
|
return false;
|
|
1063
1061
|
}
|
|
1062
|
+
if (!redirect.source.startsWith("/")) {
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1064
1065
|
if (!redirect.destination) {
|
|
1065
1066
|
return false;
|
|
1066
1067
|
}
|
|
1068
|
+
if (!testIsRedirectValid(redirect.destination)) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1067
1071
|
return true;
|
|
1068
1072
|
}
|
|
1069
1073
|
function replaceParams(urlPathFormat, params) {
|
|
@@ -1079,6 +1083,15 @@ function replaceParams(urlPathFormat, params) {
|
|
|
1079
1083
|
);
|
|
1080
1084
|
return urlPath;
|
|
1081
1085
|
}
|
|
1086
|
+
function testIsRedirectValid(url) {
|
|
1087
|
+
if (url.startsWith("/")) {
|
|
1088
|
+
return true;
|
|
1089
|
+
}
|
|
1090
|
+
if (url.match(/^https?:\/\//)) {
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
return false;
|
|
1094
|
+
}
|
|
1082
1095
|
|
|
1083
1096
|
// src/render/asset-map/dev-asset-map.ts
|
|
1084
1097
|
import path6 from "node:path";
|
|
@@ -1269,9 +1282,10 @@ async function dev(rootProjectDir, options) {
|
|
|
1269
1282
|
const server = await createDevServer({ rootDir, port });
|
|
1270
1283
|
const rootConfig = server.get("rootConfig");
|
|
1271
1284
|
const basePath = rootConfig.base || "";
|
|
1285
|
+
const homePagePath = rootConfig.server?.homePagePath || basePath;
|
|
1272
1286
|
console.log();
|
|
1273
1287
|
console.log(`${dim2("\u2503")} project: ${rootDir}`);
|
|
1274
|
-
console.log(`${dim2("\u2503")} server: http://${host}:${port}${
|
|
1288
|
+
console.log(`${dim2("\u2503")} server: http://${host}:${port}${homePagePath}`);
|
|
1275
1289
|
if (testCmsEnabled(rootConfig)) {
|
|
1276
1290
|
console.log(`${dim2("\u2503")} cms: http://${host}:${port}/cms/`);
|
|
1277
1291
|
}
|
|
@@ -1330,11 +1344,6 @@ async function createDevServer(options) {
|
|
|
1330
1344
|
plugins,
|
|
1331
1345
|
{ type: "dev", rootConfig }
|
|
1332
1346
|
);
|
|
1333
|
-
for (const plugin of plugins) {
|
|
1334
|
-
if (typeof plugin.hooks?.startup === "function") {
|
|
1335
|
-
await plugin.hooks.startup({ command: "dev", rootConfig });
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
1347
|
return server;
|
|
1339
1348
|
}
|
|
1340
1349
|
async function createViteMiddleware(options) {
|
|
@@ -1475,6 +1484,175 @@ function debounce(fn, timeout) {
|
|
|
1475
1484
|
};
|
|
1476
1485
|
}
|
|
1477
1486
|
|
|
1487
|
+
// src/cli/gae-deploy.ts
|
|
1488
|
+
import { execSync } from "node:child_process";
|
|
1489
|
+
import fs5 from "node:fs";
|
|
1490
|
+
import yaml from "js-yaml";
|
|
1491
|
+
async function gaeDeploy(appDir, options) {
|
|
1492
|
+
if (!appDir) {
|
|
1493
|
+
throw new Error(
|
|
1494
|
+
"[gae-deplopy] Missing app dir, e.g. `root gae-deploy <app dir>`"
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
const project = options?.project;
|
|
1498
|
+
if (!project) {
|
|
1499
|
+
throw new Error("[gae-deplopy] Missing: --project");
|
|
1500
|
+
}
|
|
1501
|
+
process.chdir(appDir);
|
|
1502
|
+
const appYamlPath = "app.yaml";
|
|
1503
|
+
if (!fs5.existsSync(appYamlPath)) {
|
|
1504
|
+
throw new Error(`[gae-deplopy] Missing: ${appYamlPath}`);
|
|
1505
|
+
}
|
|
1506
|
+
const appYaml = yaml.load(fs5.readFileSync(appYamlPath, "utf8"));
|
|
1507
|
+
const service = appYaml.service;
|
|
1508
|
+
if (!service) {
|
|
1509
|
+
throw new Error(
|
|
1510
|
+
'[gae-deploy] Missing service definition. Ensure "service" is defined in app.yaml.'
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
let prefix = options?.prefix;
|
|
1514
|
+
if (!prefix) {
|
|
1515
|
+
prefix = options?.promote ? "prod" : "staging";
|
|
1516
|
+
}
|
|
1517
|
+
const version = `${prefix}-${getTimestamp()}`;
|
|
1518
|
+
if (testVersionTooLong(version, service, project)) {
|
|
1519
|
+
throw new Error(`version "${version}" should not exceed 63 chars`);
|
|
1520
|
+
}
|
|
1521
|
+
console.log("[gae-deploy] \u{1F680} starting deployment...");
|
|
1522
|
+
const appYamlHasPlaceholders = testHasEnvPlaceholders(appYaml);
|
|
1523
|
+
let backupAppYaml = "";
|
|
1524
|
+
if (appYamlHasPlaceholders) {
|
|
1525
|
+
backupAppYaml = updateAppYamlEnv(appYamlPath, appYaml);
|
|
1526
|
+
}
|
|
1527
|
+
execSync(
|
|
1528
|
+
`gcloud app deploy -q --project=${project} --version=${version} --no-promote app.yaml`,
|
|
1529
|
+
{ stdio: "inherit" }
|
|
1530
|
+
);
|
|
1531
|
+
if (backupAppYaml) {
|
|
1532
|
+
fs5.copyFileSync(backupAppYaml, "app.yaml");
|
|
1533
|
+
}
|
|
1534
|
+
if (options?.healthcheckUrl) {
|
|
1535
|
+
const healthcheckPassed = await testHealth(
|
|
1536
|
+
project,
|
|
1537
|
+
service,
|
|
1538
|
+
version,
|
|
1539
|
+
options.healthcheckUrl
|
|
1540
|
+
);
|
|
1541
|
+
if (!healthcheckPassed) {
|
|
1542
|
+
throw new Error(
|
|
1543
|
+
`[gae-deploy] \u274C health check failed. check logs:
|
|
1544
|
+
${getLogsURL(
|
|
1545
|
+
project,
|
|
1546
|
+
service,
|
|
1547
|
+
version
|
|
1548
|
+
)}`
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
console.log("[gae-deploy] \u2705 health check succeeded!");
|
|
1552
|
+
}
|
|
1553
|
+
if (options?.promote) {
|
|
1554
|
+
console.log(`[gae-deploy] promoting version ${version}...`);
|
|
1555
|
+
execSync(
|
|
1556
|
+
`gcloud app -q --project=${project} services set-traffic ${service} --splits ${version}=1`,
|
|
1557
|
+
{ stdio: "inherit" }
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
if (options?.maxVersions) {
|
|
1561
|
+
const resp = execSync(
|
|
1562
|
+
`gcloud app versions list --project ${project} --service ${service} --format json`
|
|
1563
|
+
).toString();
|
|
1564
|
+
const versions = JSON.parse(resp.toString());
|
|
1565
|
+
const managedVersions = versions.filter(
|
|
1566
|
+
(appInfo) => testManagedVersion(service, appInfo, prefix)
|
|
1567
|
+
).sort(
|
|
1568
|
+
(a, b) => new Date(b.last_deployed_time.datetime).getTime() - new Date(a.last_deployed_time.datetime).getTime()
|
|
1569
|
+
);
|
|
1570
|
+
console.log(
|
|
1571
|
+
`[gae-deploy] \u{1F4E6} all managed versions (${managedVersions.length}):`
|
|
1572
|
+
);
|
|
1573
|
+
for (const eachVersion of managedVersions) {
|
|
1574
|
+
console.log(` ${eachVersion.id}`);
|
|
1575
|
+
}
|
|
1576
|
+
for (const eachVersion of managedVersions.slice(options.maxVersions - 1)) {
|
|
1577
|
+
console.log(`[gae-deploy] \u2702\uFE0F deleting old version ${eachVersion.id}`);
|
|
1578
|
+
execSync(
|
|
1579
|
+
`gcloud app versions delete -q --project=${project} --service=${service} ${eachVersion.id}`,
|
|
1580
|
+
{ stdio: "inherit" }
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
console.log(
|
|
1585
|
+
`[gae-deploy] \u2728 deployment complete
|
|
1586
|
+
versions: ${getVersionsURL(
|
|
1587
|
+
project,
|
|
1588
|
+
service
|
|
1589
|
+
)}`
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
function testVersionTooLong(version, service, project) {
|
|
1593
|
+
const subdomain = `${version}-dot-${service}-dot-${project}`;
|
|
1594
|
+
return subdomain.length > 63;
|
|
1595
|
+
}
|
|
1596
|
+
function getLogsURL(project, service, version) {
|
|
1597
|
+
return `https://console.cloud.google.com/logs/query;query=resource.type%3D"gae_app"%0Aresource.labels.module_id%3D"${service}"%0Aresource.labels.version_id%3D"${version}"?project=${project}`;
|
|
1598
|
+
}
|
|
1599
|
+
function getVersionsURL(project, service) {
|
|
1600
|
+
return `https://console.cloud.google.com/appengine/versions?project=${project}&serviceId=${service}`;
|
|
1601
|
+
}
|
|
1602
|
+
function testHasEnvPlaceholders(appYaml) {
|
|
1603
|
+
if (typeof appYaml?.env_variables !== "object") {
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1606
|
+
const envVars = appYaml.env_variables;
|
|
1607
|
+
return Object.values(envVars).some((envValue) => {
|
|
1608
|
+
return testIsEnvPlaceholder(envValue);
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
function testIsEnvPlaceholder(envValue) {
|
|
1612
|
+
if (typeof envValue === "string") {
|
|
1613
|
+
return envValue.startsWith("{") && envValue.endsWith("}");
|
|
1614
|
+
}
|
|
1615
|
+
return false;
|
|
1616
|
+
}
|
|
1617
|
+
function updateAppYamlEnv(appYamlPath, appYaml) {
|
|
1618
|
+
const backupAppYaml = `${appYamlPath}.bak`;
|
|
1619
|
+
fs5.copyFileSync(appYamlPath, backupAppYaml);
|
|
1620
|
+
let content = fs5.readFileSync(appYamlPath, "utf8");
|
|
1621
|
+
const envVars = appYaml.env_variables;
|
|
1622
|
+
Object.entries(envVars).forEach(([envVar, envValue]) => {
|
|
1623
|
+
if (testIsEnvPlaceholder(envValue)) {
|
|
1624
|
+
const value = process.env[envVar];
|
|
1625
|
+
if (!value && content.includes(`{${envVar}}`)) {
|
|
1626
|
+
console.warn(`[gae-deploy] Missing environment variable: ${envVar}`);
|
|
1627
|
+
content = content.replaceAll(`{${envVar}}`, "");
|
|
1628
|
+
} else if (value && content.includes(`{${envVar}}`)) {
|
|
1629
|
+
content = content.replaceAll(`{${envVar}}`, value);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
fs5.writeFileSync(appYamlPath, content, "utf8");
|
|
1634
|
+
return backupAppYaml;
|
|
1635
|
+
}
|
|
1636
|
+
function getTimestamp() {
|
|
1637
|
+
const pretty = (num) => (num + "").padStart(2, "0");
|
|
1638
|
+
const date = /* @__PURE__ */ new Date();
|
|
1639
|
+
return `${date.getFullYear()}${pretty(date.getMonth() + 1)}${pretty(
|
|
1640
|
+
date.getDate()
|
|
1641
|
+
)}t${pretty(date.getHours())}${pretty(date.getMinutes())}`;
|
|
1642
|
+
}
|
|
1643
|
+
async function testHealth(project, service, version, healthcheckUrl) {
|
|
1644
|
+
if (!healthcheckUrl.startsWith("/")) {
|
|
1645
|
+
healthcheckUrl = `/${healthcheckUrl}`;
|
|
1646
|
+
}
|
|
1647
|
+
const url = `https://${version}-dot-${service}-dot-${project}.appspot.com${healthcheckUrl}`;
|
|
1648
|
+
const response = await fetch(url);
|
|
1649
|
+
return response.status === 200;
|
|
1650
|
+
}
|
|
1651
|
+
function testManagedVersion(service, appInfo, prefix) {
|
|
1652
|
+
const re = new RegExp(`${prefix}-\\d{8}t\\d{4}`);
|
|
1653
|
+
return appInfo.service === service && re.exec(appInfo.id) && appInfo.traffic_split === 0;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1478
1656
|
// src/cli/preview.ts
|
|
1479
1657
|
import path8 from "node:path";
|
|
1480
1658
|
import compression from "compression";
|
|
@@ -1509,7 +1687,7 @@ async function createPreviewServer(options) {
|
|
|
1509
1687
|
server.use(cookieParser2(sessionCookieSecret));
|
|
1510
1688
|
server.use(sessionMiddleware());
|
|
1511
1689
|
const plugins = rootConfig.plugins || [];
|
|
1512
|
-
configureServerPlugins(
|
|
1690
|
+
await configureServerPlugins(
|
|
1513
1691
|
server,
|
|
1514
1692
|
async () => {
|
|
1515
1693
|
const userMiddlewares = rootConfig.server?.middlewares || [];
|
|
@@ -1539,11 +1717,6 @@ async function createPreviewServer(options) {
|
|
|
1539
1717
|
plugins,
|
|
1540
1718
|
{ type: "preview", rootConfig }
|
|
1541
1719
|
);
|
|
1542
|
-
for (const plugin of plugins) {
|
|
1543
|
-
if (typeof plugin.hooks?.startup === "function") {
|
|
1544
|
-
await plugin.hooks.startup({ command: "preview", rootConfig });
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
1720
|
return server;
|
|
1548
1721
|
}
|
|
1549
1722
|
async function rootPreviewRendererMiddleware(options) {
|
|
@@ -1660,7 +1833,7 @@ async function createProdServer(options) {
|
|
|
1660
1833
|
server.use(cookieParser3(sessionCookieSecret));
|
|
1661
1834
|
server.use(sessionMiddleware());
|
|
1662
1835
|
const plugins = rootConfig.plugins || [];
|
|
1663
|
-
configureServerPlugins(
|
|
1836
|
+
await configureServerPlugins(
|
|
1664
1837
|
server,
|
|
1665
1838
|
async () => {
|
|
1666
1839
|
const userMiddlewares = rootConfig.server?.middlewares || [];
|
|
@@ -1690,11 +1863,6 @@ async function createProdServer(options) {
|
|
|
1690
1863
|
plugins,
|
|
1691
1864
|
{ type: "prod", rootConfig }
|
|
1692
1865
|
);
|
|
1693
|
-
for (const plugin of plugins) {
|
|
1694
|
-
if (typeof plugin.hooks?.startup === "function") {
|
|
1695
|
-
await plugin.hooks.startup({ command: "start", rootConfig });
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
1866
|
return server;
|
|
1699
1867
|
}
|
|
1700
1868
|
async function rootProdRendererMiddleware(options) {
|
|
@@ -1805,11 +1973,18 @@ var CliRunner = class {
|
|
|
1805
1973
|
"-c, --concurrency <num>",
|
|
1806
1974
|
"number of files to build concurrently",
|
|
1807
1975
|
"10"
|
|
1976
|
+
).option(
|
|
1977
|
+
"--filter <urlPathRegex>",
|
|
1978
|
+
'builds the url paths that match the given regex, e.g. "/products/.*"',
|
|
1979
|
+
""
|
|
1808
1980
|
).action(build);
|
|
1809
1981
|
program.command("codegen [type] [name]").description("generates boilerplate code").option("--out <outdir>", "output dir").action(codegen);
|
|
1810
1982
|
program.command("create-package [path]").alias("package").description(
|
|
1811
1983
|
"creates a standalone npm package for deployment to various hosting services"
|
|
1812
|
-
).option("--target <target>", "hosting target, i.e. appengine or firebase").option("--out <outdir>", "output dir").option("--mode <mode>", "deployment mode, i.e. production or preview").
|
|
1984
|
+
).option("--target <target>", "hosting target, i.e. appengine or firebase").option("--out <outdir>", "output dir").option("--mode <mode>", "deployment mode, i.e. production or preview").option(
|
|
1985
|
+
"--app-yaml <path>",
|
|
1986
|
+
'for appengine targets, path to app.yaml (defaults to "app.yaml")'
|
|
1987
|
+
).action((rootPackageDir, options) => {
|
|
1813
1988
|
options.version = this.version;
|
|
1814
1989
|
createPackage(rootPackageDir, options);
|
|
1815
1990
|
});
|
|
@@ -1817,6 +1992,19 @@ var CliRunner = class {
|
|
|
1817
1992
|
"--host <host>",
|
|
1818
1993
|
"network address the server should listen on, e.g. 127.0.0.1"
|
|
1819
1994
|
).action(dev);
|
|
1995
|
+
program.command("gae-deploy <appdir>").description(
|
|
1996
|
+
"appengine deploy utility that can optionally run healthchecks before diverting traffic to the new version and clean up old versions"
|
|
1997
|
+
).option("--project <project>", "GCP project id").option("--prefix <prefix>", "prefix to append the version").option(
|
|
1998
|
+
"--promote",
|
|
1999
|
+
"whether to promote the version (if healthchecks pass)"
|
|
2000
|
+
).option(
|
|
2001
|
+
"--healthcheck-url <url>",
|
|
2002
|
+
'healthcheck url path (e.g. "/healthcheck") which should return a 200 status with the text "OK"'
|
|
2003
|
+
).option(
|
|
2004
|
+
"--max-versions <num>",
|
|
2005
|
+
"the max number of versions to keep",
|
|
2006
|
+
numberFlag
|
|
2007
|
+
).action(gaeDeploy);
|
|
1820
2008
|
program.command("preview [path]").description("starts the server in preview mode").option(
|
|
1821
2009
|
"--host <host>",
|
|
1822
2010
|
"network address the server should listen on, e.g. 127.0.0.1"
|
|
@@ -1828,6 +2016,13 @@ var CliRunner = class {
|
|
|
1828
2016
|
await program.parseAsync(argv);
|
|
1829
2017
|
}
|
|
1830
2018
|
};
|
|
2019
|
+
function numberFlag(value) {
|
|
2020
|
+
const num = parseInt(value);
|
|
2021
|
+
if (isNaN(num)) {
|
|
2022
|
+
throw new InvalidArgumentError(`not a number: ${value}`);
|
|
2023
|
+
}
|
|
2024
|
+
return num;
|
|
2025
|
+
}
|
|
1831
2026
|
|
|
1832
2027
|
export {
|
|
1833
2028
|
build,
|
|
@@ -1840,4 +2035,4 @@ export {
|
|
|
1840
2035
|
createProdServer,
|
|
1841
2036
|
CliRunner
|
|
1842
2037
|
};
|
|
1843
|
-
//# sourceMappingURL=chunk-
|
|
2038
|
+
//# sourceMappingURL=chunk-A4DNVP34.js.map
|