xpine 0.0.49 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -448,7 +448,8 @@ export default {
448
448
  includePaths: [
449
449
  // Only uses Alpine data coming from paths in this directory
450
450
  '/**/pages/secret/**/*.{js,ts,tsx,jsx}',
451
- ]
451
+ ],
452
+ requireAuthentication: true, // Only authenticated users can access this .js file
452
453
  }
453
454
  ]
454
455
  }
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import path6 from "path";
10
10
  import fs7 from "fs-extra";
11
11
  import { build } from "esbuild";
12
12
  import micromatch from "micromatch";
13
+ import minifyXML from "minify-xml";
13
14
 
14
15
  // src/build/typescript-builder.ts
15
16
  import ts from "typescript";
@@ -31,6 +32,7 @@ function fromRoot(pathName) {
31
32
  }
32
33
  var userConfig = require_default(path.join(process.cwd(), "./xpine.config.mjs")).default;
33
34
  var configDefaults = {
35
+ domain: "",
34
36
  rootDir: fromRoot(),
35
37
  srcDir: fromRoot("./src"),
36
38
  distDir: fromRoot("./dist"),
@@ -78,6 +80,9 @@ var configDefaults = {
78
80
  },
79
81
  get globalCSSFile() {
80
82
  return path.join(this.publicDir, "./styles/global.css");
83
+ },
84
+ get sitemapPath() {
85
+ return path.join(this.distPublicDir, "./sitemap.xml");
81
86
  }
82
87
  };
83
88
  var config = {
@@ -323,6 +328,10 @@ var regex_default = {
323
328
  isDynamicRoute: /\[(.*)\]/g,
324
329
  endsWithTSX: /\.tsx$/,
325
330
  endsWithJSX: /\.jsx$/,
331
+ endsWithJs: /\.js$/,
332
+ endsWithPageFileName: /\.(tsx|jsx)$/,
333
+ endsWithIndexNoExtension: /\/index$/,
334
+ endsWithIndex: /\/index\.(html|tsx|jsx|js|ts)$/,
326
335
  endsWithFileName: /\.(html|tsx|jsx|js|ts)$/,
327
336
  indexFile: /index\.(html|tsx|jsx|js|ts)$/g,
328
337
  catchAllRoute: /\/_all_$/g,
@@ -644,9 +653,29 @@ async function verifyUserMiddleware(req, _res, next) {
644
653
  }
645
654
  next();
646
655
  }
656
+ async function verifyAuthenticatedBundleMiddleware(req, res, next) {
657
+ if (!req?.originalUrl?.startsWith("/scripts/")) {
658
+ next();
659
+ return;
660
+ }
661
+ const bundle = req?.originalUrl?.split("/")?.pop()?.split(".js")?.shift();
662
+ const foundBundle = config?.bundles?.find((bundleItem) => bundleItem?.id === bundle);
663
+ if (!foundBundle) {
664
+ next();
665
+ return;
666
+ }
667
+ if (foundBundle?.requireAuthentication && !req?.user) {
668
+ res.status(403).json({
669
+ message: "Unauthenticated"
670
+ });
671
+ return;
672
+ }
673
+ next();
674
+ }
647
675
  async function createXPineRouter(app, beforeErrorRoute) {
648
- app.use(express.static(config.distPublicDir));
649
676
  app.use(verifyUserMiddleware);
677
+ app.use(verifyAuthenticatedBundleMiddleware);
678
+ app.use(express.static(config.distPublicDir));
650
679
  app.use(requestIP.mw());
651
680
  const { router, routeResults } = await createRouter();
652
681
  app.use(function replaceableRouter(req, res, next) {
@@ -755,15 +784,19 @@ import tailwindPostcss from "@tailwindcss/postcss";
755
784
  async function buildCSS(disableTailwind) {
756
785
  const cssFiles = globSync2(config.srcDir + "/**/*.css");
757
786
  for (const file of cssFiles) {
758
- const fileContents = fs6.readFileSync(file, "utf-8");
759
- let result = fileContents;
760
- if (!disableTailwind) {
761
- result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
762
- result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
787
+ try {
788
+ const fileContents = fs6.readFileSync(file, "utf-8");
789
+ let result = fileContents;
790
+ if (!disableTailwind) {
791
+ result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
792
+ result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
793
+ }
794
+ const newPath = file.replace(config.srcDir, config.distDir);
795
+ fs6.ensureFileSync(newPath);
796
+ fs6.writeFileSync(newPath, result.css);
797
+ } catch (err) {
798
+ console.error(err);
763
799
  }
764
- const newPath = file.replace(config.srcDir, config.distDir);
765
- fs6.ensureFileSync(newPath);
766
- fs6.writeFileSync(newPath, result.css);
767
800
  }
768
801
  logSize(config.distPublicDir, "css");
769
802
  }
@@ -796,18 +829,20 @@ async function buildApp(args) {
796
829
  if (!bundle?.includePaths?.length) return true;
797
830
  return micromatch([item.path], bundle?.includePaths || [])?.length;
798
831
  });
799
- const alpineDataFile2 = await buildAlpineDataFile(matchingComponentData, matchingDataFiles, bundle.id);
800
- await buildClientSideFiles([alpineDataFile2], isDev2, `./${bundle.id}.ts`, bundle.id);
832
+ const alpineDataFile = await buildAlpineDataFile(matchingComponentData, matchingDataFiles, bundle.id);
833
+ await buildClientSideFiles([alpineDataFile], isDev2, `./${bundle.id}.ts`, bundle.id);
801
834
  }
835
+ } else {
836
+ const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
837
+ await buildClientSideFiles([alpineDataFile, ...clientSideBundles], isDev2);
802
838
  }
803
- const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
804
- await buildClientSideFiles([alpineDataFile, ...clientSideBundles], isDev2);
805
839
  fs7.removeSync(config.distTempFolder);
806
840
  await buildCSS(disableTailwind);
807
841
  await buildPublicFolderSymlinks();
808
842
  await buildOnLoadFile(componentData, isDev2);
809
843
  if (!isDev2) await triggerXPineOnLoad();
810
844
  if (!isDev2) await buildFilesWithConfigs(componentData);
845
+ if (!isDev2) buildSitemap();
811
846
  if (!isDev2) context.clear();
812
847
  } catch (err) {
813
848
  console.error("Build failed");
@@ -1002,17 +1037,28 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
1002
1037
  const componentDynamicPaths = getComponentDynamicPaths(componentFileName);
1003
1038
  const componentFn = componentImport.default;
1004
1039
  if (componentImport?.onInit) await componentImport.onInit();
1005
- const outputPath = componentDynamicPaths?.length ? componentDynamicPaths.reduce((total, current) => {
1006
- return total.replace(`/[${current}]`, "");
1007
- }, path6.dirname(builtComponentPath)) : path6.dirname(builtComponentPath);
1040
+ const outputPath = determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths);
1041
+ if (!outputPath) return;
1008
1042
  if (typeof config2?.staticPaths === "boolean") {
1009
1043
  const urlPath = filePathToURLPath(outputPath);
1010
1044
  try {
1011
- const req = { params: {} };
1012
- const data = config2?.data ? await config2.data(req) : null;
1013
- const staticComponentOutput = await componentFn({ data, routePath: urlPath });
1045
+ const req = {
1046
+ params: {},
1047
+ route: {
1048
+ path: urlPath
1049
+ },
1050
+ url: urlPath
1051
+ };
1052
+ let data = config2?.data ? await config2.data(req) : null;
1053
+ data = {
1054
+ ...data,
1055
+ routePath: urlPath
1056
+ };
1057
+ const staticComponentOutput = await componentFn({ data, routePath: urlPath, req });
1058
+ const htmlOutputPath = path6.join(outputPath, "./index.html");
1059
+ fs7.ensureFileSync(htmlOutputPath);
1014
1060
  fs7.writeFileSync(
1015
- path6.join(outputPath, "./index.html"),
1061
+ htmlOutputPath,
1016
1062
  doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data, routePath: urlPath }) : staticComponentOutput) + staticComment
1017
1063
  );
1018
1064
  } catch (err) {
@@ -1023,14 +1069,19 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
1023
1069
  const dynamicPaths = await config2.staticPaths();
1024
1070
  for (const dynamicPath of dynamicPaths) {
1025
1071
  try {
1072
+ const updatedOutDir = path6.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
1073
+ const urlPath = filePathToURLPath(updatedOutDir);
1026
1074
  const req = {
1027
1075
  params: {
1028
1076
  ...componentDynamicPaths?.length ? dynamicPath : {}
1029
- }
1077
+ },
1078
+ route: {
1079
+ path: urlPath
1080
+ },
1081
+ url: urlPath
1030
1082
  };
1031
- const updatedOutDir = path6.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
1032
- const urlPath = filePathToURLPath(updatedOutDir);
1033
- const data = config2?.data ? await config2.data(req) : null;
1083
+ let data = config2?.data ? await config2.data(req) : null;
1084
+ data = { ...data, routePath: urlPath };
1034
1085
  const staticComponentOutput = await componentFn({ req, data, routePath: urlPath });
1035
1086
  fs7.ensureDirSync(updatedOutDir);
1036
1087
  fs7.writeFileSync(
@@ -1044,6 +1095,20 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
1044
1095
  }
1045
1096
  }
1046
1097
  }
1098
+ function determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths) {
1099
+ if (componentDynamicPaths?.length) {
1100
+ return componentDynamicPaths.reduce((total, current) => {
1101
+ return total.replace(`/[${current}]`, "");
1102
+ }, path6.dirname(builtComponentPath));
1103
+ } else {
1104
+ if (builtComponentPath?.match(regex_default.endsWithIndex)) {
1105
+ return path6.dirname(builtComponentPath);
1106
+ } else {
1107
+ if (!builtComponentPath.match(regex_default.endsWithJs)) return null;
1108
+ return builtComponentPath.replace(regex_default.endsWithJs, "");
1109
+ }
1110
+ }
1111
+ }
1047
1112
  function getComponentDynamicPaths(componentPath) {
1048
1113
  const matches = [...componentPath.matchAll(regex_default.dynamicRoutes)].concat([...componentPath.matchAll(regex_default.catchAllRoute)]);
1049
1114
  if (!matches?.length) return null;
@@ -1100,6 +1165,47 @@ async function buildOnLoadFile(componentData, isDev2) {
1100
1165
  ]
1101
1166
  });
1102
1167
  }
1168
+ async function buildSitemap() {
1169
+ if (!config?.sitemap) return;
1170
+ const staticPages = globSync3(`${config.distPagesDir}/**/*.html`) || [];
1171
+ const dynamicPages = globSync3(`${config.pagesDir}/**/*.{jsx,tsx}`) || [];
1172
+ const allPages = staticPages.concat(dynamicPages);
1173
+ const filteredPages = allPages.filter((filePath) => {
1174
+ return !micromatch([filePath], config.sitemap?.excludePaths || [])?.length;
1175
+ });
1176
+ const pages = filteredPages.map((filePath) => {
1177
+ const replacedExtensions = filePath.replace(regex_default.endsWithFileName, "");
1178
+ const replacedPagesPath = replacedExtensions.replace(config.pagesDir, "");
1179
+ const replacedDistPath = replacedPagesPath.replace(config.distPagesDir, "");
1180
+ const replacedIndex = replacedDistPath.replace(regex_default.endsWithIndexNoExtension, "");
1181
+ if (replacedIndex === "") return "/";
1182
+ return replacedIndex;
1183
+ }).filter((filePath) => {
1184
+ return !filePath.includes("+config");
1185
+ });
1186
+ const sitemap = `
1187
+ <?xml version="1.0" encoding="UTF-8"?>
1188
+ <urlset
1189
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
1190
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1191
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
1192
+ xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
1193
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
1194
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
1195
+ xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
1196
+ >
1197
+ ${pages.map((page) => {
1198
+ return `
1199
+ <url>
1200
+ <loc>${config?.domain ? `https://${config.domain}${page}` : page}</loc>
1201
+ </url>
1202
+ `;
1203
+ }).join("\n")}
1204
+ </urlset>
1205
+ `;
1206
+ fs7.ensureFileSync(config.sitemapPath);
1207
+ fs7.writeFileSync(config.sitemapPath, minifyXML(sitemap));
1208
+ }
1103
1209
 
1104
1210
  // src/runDevServer.ts
1105
1211
  import path8 from "path";
@@ -1131,6 +1237,7 @@ async function loadSecretsManagerSecrets() {
1131
1237
  client.destroy();
1132
1238
  } catch (err) {
1133
1239
  console.error(`Could not load secret: ${process.env.SECRET_NAME}`);
1240
+ console.error(err);
1134
1241
  return null;
1135
1242
  }
1136
1243
  }
@@ -1256,12 +1363,14 @@ export {
1256
1363
  buildFilesWithConfigs,
1257
1364
  buildOnLoadFile,
1258
1365
  buildPublicFolderSymlinks,
1366
+ buildSitemap,
1259
1367
  buildStaticFiles,
1260
1368
  config,
1261
1369
  context,
1262
1370
  createContext,
1263
1371
  createRouter,
1264
1372
  createXPineRouter,
1373
+ determineOutputPathForStaticPath,
1265
1374
  filePathToURLPath,
1266
1375
  fromRoot,
1267
1376
  getComponentDynamicPaths,
@@ -1 +1 @@
1
- {"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../../../src/build/css.ts"],"names":[],"mappings":"AAUA,wBAAsB,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,iBAgBvD"}
1
+ {"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../../../src/build/css.ts"],"names":[],"mappings":"AAUA,wBAAsB,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,iBAoBvD"}
@@ -1 +1 @@
1
- {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAgB,OAAO,EAAqB,MAAM,SAAS,CAAC;AAqJ5E,wBAAsB,YAAY;;;GAkCjC;AAiBD,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,iBAyB1F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,KAAK,CAanG;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,UAQzE"}
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAgB,OAAO,EAAqB,MAAM,SAAS,CAAC;AAqJ5E,wBAAsB,YAAY;;;GAkCjC;AAsCD,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,iBA0B1F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,KAAK,CAanG;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,UAQzE"}
@@ -9,11 +9,13 @@ export declare function buildPublicFolderSymlinks(): Promise<void>;
9
9
  export declare function logSize(pathName: string, type: string, validExtensions?: string[]): Promise<void>;
10
10
  export declare function buildFilesWithConfigs(componentData: ComponentData[]): Promise<void>;
11
11
  export declare function buildStaticFiles(config: ConfigFile, component: ComponentData, componentImport: any, builtComponentPath: string): Promise<void>;
12
+ export declare function determineOutputPathForStaticPath(builtComponentPath: string, componentDynamicPaths?: string[]): string;
12
13
  export declare function getComponentDynamicPaths(componentPath: string): string[];
13
14
  export type OnLoadFileResult = {
14
15
  imports: string;
15
16
  fn: string;
16
17
  };
17
18
  export declare function buildOnLoadFile(componentData: ComponentData[], isDev?: boolean): Promise<void>;
19
+ export declare function buildSitemap(): Promise<void>;
18
20
  export {};
19
21
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/scripts/build.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,UAAU,EAA2B,aAAa,EAAE,MAAM,aAAa,CAAC;AAajF,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAA;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,iBA+ChD;AAwJD,wBAAsB,yBAAyB,kBAgB9C;AAED,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,WAAkB,iBAa9F;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,aAAa,EAAE,iBAezE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,GAAG,EAAE,kBAAkB,EAAE,MAAM,iBAyEpI;AAED,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAQxE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAA;AAGD,wBAAsB,eAAe,CAAC,aAAa,EAAE,aAAa,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,iBAoDpF"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/scripts/build.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,UAAU,EAA2B,aAAa,EAAE,MAAM,aAAa,CAAC;AAajF,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAA;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,iBAgDhD;AAwJD,wBAAsB,yBAAyB,kBAgB9C;AAED,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,WAAkB,iBAa9F;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,aAAa,EAAE,iBAezE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,GAAG,EAAE,kBAAkB,EAAE,MAAM,iBA2FpI;AAED,wBAAgB,gCAAgC,CAAC,kBAAkB,EAAE,MAAM,EAAE,qBAAqB,CAAC,EAAE,MAAM,EAAE,UAe5G;AAED,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAQxE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAA;AAGD,wBAAsB,eAAe,CAAC,aAAa,EAAE,aAAa,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,iBAoDpF;AAED,wBAAsB,YAAY,kBA0CjC"}
@@ -5,6 +5,7 @@ import path5 from "path";
5
5
  import fs7 from "fs-extra";
6
6
  import { build } from "esbuild";
7
7
  import micromatch from "micromatch";
8
+ import minifyXML from "minify-xml";
8
9
 
9
10
  // src/build/typescript-builder.ts
10
11
  import ts from "typescript";
@@ -26,6 +27,7 @@ function fromRoot(pathName) {
26
27
  }
27
28
  var userConfig = require_default(path.join(process.cwd(), "./xpine.config.mjs")).default;
28
29
  var configDefaults = {
30
+ domain: "",
29
31
  rootDir: fromRoot(),
30
32
  srcDir: fromRoot("./src"),
31
33
  distDir: fromRoot("./dist"),
@@ -73,6 +75,9 @@ var configDefaults = {
73
75
  },
74
76
  get globalCSSFile() {
75
77
  return path.join(this.publicDir, "./styles/global.css");
78
+ },
79
+ get sitemapPath() {
80
+ return path.join(this.distPublicDir, "./sitemap.xml");
76
81
  }
77
82
  };
78
83
  var config = {
@@ -318,6 +323,10 @@ var regex_default = {
318
323
  isDynamicRoute: /\[(.*)\]/g,
319
324
  endsWithTSX: /\.tsx$/,
320
325
  endsWithJSX: /\.jsx$/,
326
+ endsWithJs: /\.js$/,
327
+ endsWithPageFileName: /\.(tsx|jsx)$/,
328
+ endsWithIndexNoExtension: /\/index$/,
329
+ endsWithIndex: /\/index\.(html|tsx|jsx|js|ts)$/,
321
330
  endsWithFileName: /\.(html|tsx|jsx|js|ts)$/,
322
331
  indexFile: /index\.(html|tsx|jsx|js|ts)$/g,
323
332
  catchAllRoute: /\/_all_$/g,
@@ -548,15 +557,19 @@ import tailwindPostcss from "@tailwindcss/postcss";
548
557
  async function buildCSS(disableTailwind2) {
549
558
  const cssFiles = globSync2(config.srcDir + "/**/*.css");
550
559
  for (const file of cssFiles) {
551
- const fileContents = fs6.readFileSync(file, "utf-8");
552
- let result = fileContents;
553
- if (!disableTailwind2) {
554
- result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
555
- result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
560
+ try {
561
+ const fileContents = fs6.readFileSync(file, "utf-8");
562
+ let result = fileContents;
563
+ if (!disableTailwind2) {
564
+ result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
565
+ result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
566
+ }
567
+ const newPath = file.replace(config.srcDir, config.distDir);
568
+ fs6.ensureFileSync(newPath);
569
+ fs6.writeFileSync(newPath, result.css);
570
+ } catch (err) {
571
+ console.error(err);
556
572
  }
557
- const newPath = file.replace(config.srcDir, config.distDir);
558
- fs6.ensureFileSync(newPath);
559
- fs6.writeFileSync(newPath, result.css);
560
573
  }
561
574
  logSize(config.distPublicDir, "css");
562
575
  }
@@ -589,18 +602,20 @@ async function buildApp(args) {
589
602
  if (!bundle?.includePaths?.length) return true;
590
603
  return micromatch([item.path], bundle?.includePaths || [])?.length;
591
604
  });
592
- const alpineDataFile2 = await buildAlpineDataFile(matchingComponentData, matchingDataFiles, bundle.id);
593
- await buildClientSideFiles([alpineDataFile2], isDev3, `./${bundle.id}.ts`, bundle.id);
605
+ const alpineDataFile = await buildAlpineDataFile(matchingComponentData, matchingDataFiles, bundle.id);
606
+ await buildClientSideFiles([alpineDataFile], isDev3, `./${bundle.id}.ts`, bundle.id);
594
607
  }
608
+ } else {
609
+ const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
610
+ await buildClientSideFiles([alpineDataFile, ...clientSideBundles], isDev3);
595
611
  }
596
- const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
597
- await buildClientSideFiles([alpineDataFile, ...clientSideBundles], isDev3);
598
612
  fs7.removeSync(config.distTempFolder);
599
613
  await buildCSS(disableTailwind2);
600
614
  await buildPublicFolderSymlinks();
601
615
  await buildOnLoadFile(componentData, isDev3);
602
616
  if (!isDev3) await triggerXPineOnLoad();
603
617
  if (!isDev3) await buildFilesWithConfigs(componentData);
618
+ if (!isDev3) buildSitemap();
604
619
  if (!isDev3) context.clear();
605
620
  } catch (err) {
606
621
  console.error("Build failed");
@@ -795,17 +810,28 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
795
810
  const componentDynamicPaths = getComponentDynamicPaths(componentFileName);
796
811
  const componentFn = componentImport.default;
797
812
  if (componentImport?.onInit) await componentImport.onInit();
798
- const outputPath = componentDynamicPaths?.length ? componentDynamicPaths.reduce((total, current) => {
799
- return total.replace(`/[${current}]`, "");
800
- }, path5.dirname(builtComponentPath)) : path5.dirname(builtComponentPath);
813
+ const outputPath = determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths);
814
+ if (!outputPath) return;
801
815
  if (typeof config2?.staticPaths === "boolean") {
802
816
  const urlPath = filePathToURLPath(outputPath);
803
817
  try {
804
- const req = { params: {} };
805
- const data = config2?.data ? await config2.data(req) : null;
806
- const staticComponentOutput = await componentFn({ data, routePath: urlPath });
818
+ const req = {
819
+ params: {},
820
+ route: {
821
+ path: urlPath
822
+ },
823
+ url: urlPath
824
+ };
825
+ let data = config2?.data ? await config2.data(req) : null;
826
+ data = {
827
+ ...data,
828
+ routePath: urlPath
829
+ };
830
+ const staticComponentOutput = await componentFn({ data, routePath: urlPath, req });
831
+ const htmlOutputPath = path5.join(outputPath, "./index.html");
832
+ fs7.ensureFileSync(htmlOutputPath);
807
833
  fs7.writeFileSync(
808
- path5.join(outputPath, "./index.html"),
834
+ htmlOutputPath,
809
835
  doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data, routePath: urlPath }) : staticComponentOutput) + staticComment
810
836
  );
811
837
  } catch (err) {
@@ -816,14 +842,19 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
816
842
  const dynamicPaths = await config2.staticPaths();
817
843
  for (const dynamicPath of dynamicPaths) {
818
844
  try {
845
+ const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
846
+ const urlPath = filePathToURLPath(updatedOutDir);
819
847
  const req = {
820
848
  params: {
821
849
  ...componentDynamicPaths?.length ? dynamicPath : {}
822
- }
850
+ },
851
+ route: {
852
+ path: urlPath
853
+ },
854
+ url: urlPath
823
855
  };
824
- const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
825
- const urlPath = filePathToURLPath(updatedOutDir);
826
- const data = config2?.data ? await config2.data(req) : null;
856
+ let data = config2?.data ? await config2.data(req) : null;
857
+ data = { ...data, routePath: urlPath };
827
858
  const staticComponentOutput = await componentFn({ req, data, routePath: urlPath });
828
859
  fs7.ensureDirSync(updatedOutDir);
829
860
  fs7.writeFileSync(
@@ -837,6 +868,20 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
837
868
  }
838
869
  }
839
870
  }
871
+ function determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths) {
872
+ if (componentDynamicPaths?.length) {
873
+ return componentDynamicPaths.reduce((total, current) => {
874
+ return total.replace(`/[${current}]`, "");
875
+ }, path5.dirname(builtComponentPath));
876
+ } else {
877
+ if (builtComponentPath?.match(regex_default.endsWithIndex)) {
878
+ return path5.dirname(builtComponentPath);
879
+ } else {
880
+ if (!builtComponentPath.match(regex_default.endsWithJs)) return null;
881
+ return builtComponentPath.replace(regex_default.endsWithJs, "");
882
+ }
883
+ }
884
+ }
840
885
  function getComponentDynamicPaths(componentPath) {
841
886
  const matches = [...componentPath.matchAll(regex_default.dynamicRoutes)].concat([...componentPath.matchAll(regex_default.catchAllRoute)]);
842
887
  if (!matches?.length) return null;
@@ -893,6 +938,47 @@ async function buildOnLoadFile(componentData, isDev3) {
893
938
  ]
894
939
  });
895
940
  }
941
+ async function buildSitemap() {
942
+ if (!config?.sitemap) return;
943
+ const staticPages = globSync3(`${config.distPagesDir}/**/*.html`) || [];
944
+ const dynamicPages = globSync3(`${config.pagesDir}/**/*.{jsx,tsx}`) || [];
945
+ const allPages = staticPages.concat(dynamicPages);
946
+ const filteredPages = allPages.filter((filePath) => {
947
+ return !micromatch([filePath], config.sitemap?.excludePaths || [])?.length;
948
+ });
949
+ const pages = filteredPages.map((filePath) => {
950
+ const replacedExtensions = filePath.replace(regex_default.endsWithFileName, "");
951
+ const replacedPagesPath = replacedExtensions.replace(config.pagesDir, "");
952
+ const replacedDistPath = replacedPagesPath.replace(config.distPagesDir, "");
953
+ const replacedIndex = replacedDistPath.replace(regex_default.endsWithIndexNoExtension, "");
954
+ if (replacedIndex === "") return "/";
955
+ return replacedIndex;
956
+ }).filter((filePath) => {
957
+ return !filePath.includes("+config");
958
+ });
959
+ const sitemap = `
960
+ <?xml version="1.0" encoding="UTF-8"?>
961
+ <urlset
962
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
963
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
964
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
965
+ xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
966
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
967
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
968
+ xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
969
+ >
970
+ ${pages.map((page) => {
971
+ return `
972
+ <url>
973
+ <loc>${config?.domain ? `https://${config.domain}${page}` : page}</loc>
974
+ </url>
975
+ `;
976
+ }).join("\n")}
977
+ </urlset>
978
+ `;
979
+ fs7.ensureFileSync(config.sitemapPath);
980
+ fs7.writeFileSync(config.sitemapPath, minifyXML(sitemap));
981
+ }
896
982
 
897
983
  // src/scripts/xpine-build.ts
898
984
  import yargs from "yargs";
@@ -12,6 +12,7 @@ import path5 from "path";
12
12
  import fs7 from "fs-extra";
13
13
  import { build } from "esbuild";
14
14
  import micromatch from "micromatch";
15
+ import minifyXML from "minify-xml";
15
16
 
16
17
  // src/build/typescript-builder.ts
17
18
  import ts from "typescript";
@@ -33,6 +34,7 @@ function fromRoot(pathName) {
33
34
  }
34
35
  var userConfig = require_default(path.join(process.cwd(), "./xpine.config.mjs")).default;
35
36
  var configDefaults = {
37
+ domain: "",
36
38
  rootDir: fromRoot(),
37
39
  srcDir: fromRoot("./src"),
38
40
  distDir: fromRoot("./dist"),
@@ -80,6 +82,9 @@ var configDefaults = {
80
82
  },
81
83
  get globalCSSFile() {
82
84
  return path.join(this.publicDir, "./styles/global.css");
85
+ },
86
+ get sitemapPath() {
87
+ return path.join(this.distPublicDir, "./sitemap.xml");
83
88
  }
84
89
  };
85
90
  var config = {
@@ -325,6 +330,10 @@ var regex_default = {
325
330
  isDynamicRoute: /\[(.*)\]/g,
326
331
  endsWithTSX: /\.tsx$/,
327
332
  endsWithJSX: /\.jsx$/,
333
+ endsWithJs: /\.js$/,
334
+ endsWithPageFileName: /\.(tsx|jsx)$/,
335
+ endsWithIndexNoExtension: /\/index$/,
336
+ endsWithIndex: /\/index\.(html|tsx|jsx|js|ts)$/,
328
337
  endsWithFileName: /\.(html|tsx|jsx|js|ts)$/,
329
338
  indexFile: /index\.(html|tsx|jsx|js|ts)$/g,
330
339
  catchAllRoute: /\/_all_$/g,
@@ -555,15 +564,19 @@ import tailwindPostcss from "@tailwindcss/postcss";
555
564
  async function buildCSS(disableTailwind) {
556
565
  const cssFiles = globSync2(config.srcDir + "/**/*.css");
557
566
  for (const file of cssFiles) {
558
- const fileContents = fs6.readFileSync(file, "utf-8");
559
- let result = fileContents;
560
- if (!disableTailwind) {
561
- result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
562
- result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
567
+ try {
568
+ const fileContents = fs6.readFileSync(file, "utf-8");
569
+ let result = fileContents;
570
+ if (!disableTailwind) {
571
+ result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
572
+ result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
573
+ }
574
+ const newPath = file.replace(config.srcDir, config.distDir);
575
+ fs6.ensureFileSync(newPath);
576
+ fs6.writeFileSync(newPath, result.css);
577
+ } catch (err) {
578
+ console.error(err);
563
579
  }
564
- const newPath = file.replace(config.srcDir, config.distDir);
565
- fs6.ensureFileSync(newPath);
566
- fs6.writeFileSync(newPath, result.css);
567
580
  }
568
581
  logSize(config.distPublicDir, "css");
569
582
  }
@@ -596,18 +609,20 @@ async function buildApp(args) {
596
609
  if (!bundle?.includePaths?.length) return true;
597
610
  return micromatch([item.path], bundle?.includePaths || [])?.length;
598
611
  });
599
- const alpineDataFile2 = await buildAlpineDataFile(matchingComponentData, matchingDataFiles, bundle.id);
600
- await buildClientSideFiles([alpineDataFile2], isDev2, `./${bundle.id}.ts`, bundle.id);
612
+ const alpineDataFile = await buildAlpineDataFile(matchingComponentData, matchingDataFiles, bundle.id);
613
+ await buildClientSideFiles([alpineDataFile], isDev2, `./${bundle.id}.ts`, bundle.id);
601
614
  }
615
+ } else {
616
+ const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
617
+ await buildClientSideFiles([alpineDataFile, ...clientSideBundles], isDev2);
602
618
  }
603
- const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
604
- await buildClientSideFiles([alpineDataFile, ...clientSideBundles], isDev2);
605
619
  fs7.removeSync(config.distTempFolder);
606
620
  await buildCSS(disableTailwind);
607
621
  await buildPublicFolderSymlinks();
608
622
  await buildOnLoadFile(componentData, isDev2);
609
623
  if (!isDev2) await triggerXPineOnLoad();
610
624
  if (!isDev2) await buildFilesWithConfigs(componentData);
625
+ if (!isDev2) buildSitemap();
611
626
  if (!isDev2) context.clear();
612
627
  } catch (err) {
613
628
  console.error("Build failed");
@@ -802,17 +817,28 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
802
817
  const componentDynamicPaths = getComponentDynamicPaths(componentFileName);
803
818
  const componentFn = componentImport.default;
804
819
  if (componentImport?.onInit) await componentImport.onInit();
805
- const outputPath = componentDynamicPaths?.length ? componentDynamicPaths.reduce((total, current) => {
806
- return total.replace(`/[${current}]`, "");
807
- }, path5.dirname(builtComponentPath)) : path5.dirname(builtComponentPath);
820
+ const outputPath = determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths);
821
+ if (!outputPath) return;
808
822
  if (typeof config2?.staticPaths === "boolean") {
809
823
  const urlPath = filePathToURLPath(outputPath);
810
824
  try {
811
- const req = { params: {} };
812
- const data = config2?.data ? await config2.data(req) : null;
813
- const staticComponentOutput = await componentFn({ data, routePath: urlPath });
825
+ const req = {
826
+ params: {},
827
+ route: {
828
+ path: urlPath
829
+ },
830
+ url: urlPath
831
+ };
832
+ let data = config2?.data ? await config2.data(req) : null;
833
+ data = {
834
+ ...data,
835
+ routePath: urlPath
836
+ };
837
+ const staticComponentOutput = await componentFn({ data, routePath: urlPath, req });
838
+ const htmlOutputPath = path5.join(outputPath, "./index.html");
839
+ fs7.ensureFileSync(htmlOutputPath);
814
840
  fs7.writeFileSync(
815
- path5.join(outputPath, "./index.html"),
841
+ htmlOutputPath,
816
842
  doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data, routePath: urlPath }) : staticComponentOutput) + staticComment
817
843
  );
818
844
  } catch (err) {
@@ -823,14 +849,19 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
823
849
  const dynamicPaths = await config2.staticPaths();
824
850
  for (const dynamicPath of dynamicPaths) {
825
851
  try {
852
+ const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
853
+ const urlPath = filePathToURLPath(updatedOutDir);
826
854
  const req = {
827
855
  params: {
828
856
  ...componentDynamicPaths?.length ? dynamicPath : {}
829
- }
857
+ },
858
+ route: {
859
+ path: urlPath
860
+ },
861
+ url: urlPath
830
862
  };
831
- const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
832
- const urlPath = filePathToURLPath(updatedOutDir);
833
- const data = config2?.data ? await config2.data(req) : null;
863
+ let data = config2?.data ? await config2.data(req) : null;
864
+ data = { ...data, routePath: urlPath };
834
865
  const staticComponentOutput = await componentFn({ req, data, routePath: urlPath });
835
866
  fs7.ensureDirSync(updatedOutDir);
836
867
  fs7.writeFileSync(
@@ -844,6 +875,20 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
844
875
  }
845
876
  }
846
877
  }
878
+ function determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths) {
879
+ if (componentDynamicPaths?.length) {
880
+ return componentDynamicPaths.reduce((total, current) => {
881
+ return total.replace(`/[${current}]`, "");
882
+ }, path5.dirname(builtComponentPath));
883
+ } else {
884
+ if (builtComponentPath?.match(regex_default.endsWithIndex)) {
885
+ return path5.dirname(builtComponentPath);
886
+ } else {
887
+ if (!builtComponentPath.match(regex_default.endsWithJs)) return null;
888
+ return builtComponentPath.replace(regex_default.endsWithJs, "");
889
+ }
890
+ }
891
+ }
847
892
  function getComponentDynamicPaths(componentPath) {
848
893
  const matches = [...componentPath.matchAll(regex_default.dynamicRoutes)].concat([...componentPath.matchAll(regex_default.catchAllRoute)]);
849
894
  if (!matches?.length) return null;
@@ -900,6 +945,47 @@ async function buildOnLoadFile(componentData, isDev2) {
900
945
  ]
901
946
  });
902
947
  }
948
+ async function buildSitemap() {
949
+ if (!config?.sitemap) return;
950
+ const staticPages = globSync3(`${config.distPagesDir}/**/*.html`) || [];
951
+ const dynamicPages = globSync3(`${config.pagesDir}/**/*.{jsx,tsx}`) || [];
952
+ const allPages = staticPages.concat(dynamicPages);
953
+ const filteredPages = allPages.filter((filePath) => {
954
+ return !micromatch([filePath], config.sitemap?.excludePaths || [])?.length;
955
+ });
956
+ const pages = filteredPages.map((filePath) => {
957
+ const replacedExtensions = filePath.replace(regex_default.endsWithFileName, "");
958
+ const replacedPagesPath = replacedExtensions.replace(config.pagesDir, "");
959
+ const replacedDistPath = replacedPagesPath.replace(config.distPagesDir, "");
960
+ const replacedIndex = replacedDistPath.replace(regex_default.endsWithIndexNoExtension, "");
961
+ if (replacedIndex === "") return "/";
962
+ return replacedIndex;
963
+ }).filter((filePath) => {
964
+ return !filePath.includes("+config");
965
+ });
966
+ const sitemap = `
967
+ <?xml version="1.0" encoding="UTF-8"?>
968
+ <urlset
969
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
970
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
971
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
972
+ xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
973
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
974
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
975
+ xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
976
+ >
977
+ ${pages.map((page) => {
978
+ return `
979
+ <url>
980
+ <loc>${config?.domain ? `https://${config.domain}${page}` : page}</loc>
981
+ </url>
982
+ `;
983
+ }).join("\n")}
984
+ </urlset>
985
+ `;
986
+ fs7.ensureFileSync(config.sitemapPath);
987
+ fs7.writeFileSync(config.sitemapPath, minifyXML(sitemap));
988
+ }
903
989
 
904
990
  // src/runDevServer.ts
905
991
  import path7 from "path";
@@ -931,6 +1017,7 @@ async function loadSecretsManagerSecrets() {
931
1017
  client.destroy();
932
1018
  } catch (err) {
933
1019
  console.error(`Could not load secret: ${process.env.SECRET_NAME}`);
1020
+ console.error(err);
934
1021
  return null;
935
1022
  }
936
1023
  }
@@ -1 +1 @@
1
- {"version":3,"file":"get-config.d.ts","sourceRoot":"","sources":["../../../src/util/get-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,wBAAgB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,UAGzC;AAuDD,eAAO,MAAM,MAAM,EAAE,WAGpB,CAAC"}
1
+ {"version":3,"file":"get-config.d.ts","sourceRoot":"","sources":["../../../src/util/get-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,wBAAgB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,UAGzC;AA2DD,eAAO,MAAM,MAAM,EAAE,WAGpB,CAAC"}
@@ -6,6 +6,10 @@ declare const _default: {
6
6
  isDynamicRoute: RegExp;
7
7
  endsWithTSX: RegExp;
8
8
  endsWithJSX: RegExp;
9
+ endsWithJs: RegExp;
10
+ endsWithPageFileName: RegExp;
11
+ endsWithIndexNoExtension: RegExp;
12
+ endsWithIndex: RegExp;
9
13
  endsWithFileName: RegExp;
10
14
  indexFile: RegExp;
11
15
  catchAllRoute: RegExp;
@@ -1 +1 @@
1
- {"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../../../src/util/regex.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,wBAaE"}
1
+ {"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../../../src/util/regex.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,wBAiBE"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "xpine",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "main": "dist/index.js",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-secrets-manager": "^3.758.0",
7
- "@tailwindcss/postcss": "^4.0.8",
7
+ "@tailwindcss/postcss": "^4.1.12",
8
8
  "builtin-modules": "^4.0.0",
9
9
  "chokidar": "^4.0.3",
10
10
  "dotenv": "^16.4.7",
@@ -15,6 +15,7 @@
15
15
  "glob": "^11.0.1",
16
16
  "jsonwebtoken": "^9.0.2",
17
17
  "micromatch": "^4.0.8",
18
+ "minify-xml": "^4.5.2",
18
19
  "postcss": "^8.5.3",
19
20
  "request-ip": "^3.3.0",
20
21
  "shelljs": "^0.9.2",