@examplary/cli 1.13.1 → 1.14.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.
@@ -38,6 +38,19 @@ export const uploadCommand = async (argv) => {
38
38
  console.log(`• Uploaded component ${componentType}`);
39
39
  }
40
40
  definition.components = components;
41
+ // Upload the bundled functions
42
+ const functions = {};
43
+ for (const [functionName, output] of Object.entries(result.functions)) {
44
+ // Upload source map first if it exists
45
+ let sourceMapStatement = "";
46
+ if (output.sourcemap) {
47
+ const url = await uploadFile(`function-${functionName}.js.map`, output.sourcemap, "application/json");
48
+ sourceMapStatement = `\n//# sourceMappingURL=${url}`;
49
+ }
50
+ functions[functionName] = await uploadFile(`function-${functionName}.js`, output.js + sourceMapStatement, "application/javascript");
51
+ console.log(`• Uploaded function ${functionName}`);
52
+ }
53
+ definition.functions = functions;
41
54
  // Upload styles if present
42
55
  if (result.styles) {
43
56
  definition.stylesheet = await uploadFile("styles.css", result.styles, "text/css");
@@ -1,4 +1,10 @@
1
1
  export declare const buildComponent: (file: string) => Promise<{
2
2
  js: string;
3
3
  sourcemap: string;
4
+ sha256: string;
5
+ }>;
6
+ export declare const buildFunction: (file: string) => Promise<{
7
+ js: string;
8
+ sourcemap: string;
9
+ sha256: string;
4
10
  }>;
@@ -1,4 +1,8 @@
1
+ import { createHash } from "crypto";
1
2
  import { build } from "esbuild";
3
+ const calculateHash = (compiled) => {
4
+ return createHash("sha256").update(compiled).digest("hex");
5
+ };
2
6
  export const buildComponent = async (file) => {
3
7
  const res = await build({
4
8
  entryPoints: [file],
@@ -23,5 +27,28 @@ export const buildComponent = async (file) => {
23
27
  sourcemap = Buffer.from(js.split(sourceMapStatement)[1].trim(), "base64").toString("utf-8");
24
28
  js = js.split(sourceMapStatement)[0].trim();
25
29
  }
26
- return { js, sourcemap };
30
+ const sha256 = calculateHash(js);
31
+ return { js, sourcemap, sha256 };
32
+ };
33
+ export const buildFunction = async (file) => {
34
+ const res = await build({
35
+ entryPoints: [file],
36
+ bundle: true,
37
+ write: false,
38
+ minify: true,
39
+ sourcemap: "inline",
40
+ platform: "neutral",
41
+ mainFields: ["module", "main"],
42
+ format: "iife",
43
+ globalName: "userFunction",
44
+ });
45
+ let js = res.outputFiles[0].text;
46
+ let sourcemap = "";
47
+ const sourceMapStatement = "\n//# sourceMappingURL=data:application/json;base64,";
48
+ if (js.includes(sourceMapStatement)) {
49
+ sourcemap = Buffer.from(js.split(sourceMapStatement)[1].trim(), "base64").toString("utf-8");
50
+ js = js.split(sourceMapStatement)[0].trim();
51
+ }
52
+ const sha256 = calculateHash(js);
53
+ return { js, sourcemap, sha256 };
27
54
  };
@@ -18,6 +18,8 @@ export type ValidationResult = {
18
18
  styles?: string;
19
19
  /** Raw icon file contents, present only when an icon is configured. */
20
20
  iconContents?: string;
21
+ /** Bundled function output, keyed by function name, ready to upload. */
22
+ functions: Record<string, BuiltComponent>;
21
23
  };
22
24
  /**
23
25
  * Runs every check the `upload` command performs, short of actually uploading:
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "fs/promises";
2
2
  import { resolve } from "path";
3
- import { buildComponent } from "./bundle.js";
3
+ import { buildComponent, buildFunction } from "./bundle.js";
4
4
  import { deepCompileJsonata } from "./jsonata.js";
5
5
  import { runResponseProcessingTestCases, } from "./response-processing.js";
6
6
  import { buildStyles } from "./styles.js";
@@ -20,6 +20,7 @@ export const validateDefinition = async (definition, dir) => {
20
20
  const errors = [];
21
21
  const warnings = [];
22
22
  const bundles = {};
23
+ const functions = {};
23
24
  let styles;
24
25
  let iconContents;
25
26
  if (!definition.id)
@@ -89,6 +90,21 @@ export const validateDefinition = async (definition, dir) => {
89
90
  errors.push(`Error bundling component ${componentType} from ${componentPath}: ${error.message}`);
90
91
  }
91
92
  }
93
+ // Bundle functions to confirm they build and to capture their output
94
+ for (const [functionName, functionPath] of Object.entries(definition.functions || {})) {
95
+ const realPath = resolve(dir, functionPath);
96
+ try {
97
+ const output = await buildFunction(realPath);
98
+ functions[functionName] = output;
99
+ const sizeInKb = output.js.length / 1000;
100
+ if (sizeInKb > 100) {
101
+ errors.push(`Function ${functionName} is larger than the 100 kB limit (${sizeInKb.toFixed(2)} kB)`);
102
+ }
103
+ }
104
+ catch (error) {
105
+ errors.push(`Error bundling function ${functionName} from ${functionPath}: ${error.message}`);
106
+ }
107
+ }
92
108
  // Build styles if any components are present
93
109
  if (Object.keys(bundles).length) {
94
110
  styles = (await buildStyles()) ?? undefined;
@@ -106,7 +122,15 @@ export const validateDefinition = async (definition, dir) => {
106
122
  errors.push(`Icon file is empty: ${iconPath}`);
107
123
  }
108
124
  }
109
- return { errors, warnings, testCaseResults, bundles, styles, iconContents };
125
+ return {
126
+ errors,
127
+ warnings,
128
+ testCaseResults,
129
+ bundles,
130
+ functions,
131
+ styles,
132
+ iconContents,
133
+ };
110
134
  };
111
135
  /** Whether the definition is safe to upload. */
112
136
  export const isValid = (result) => result.errors.length === 0 && result.testCaseResults.every((r) => r.passed);
@@ -116,6 +140,10 @@ export const printValidationResult = (result) => {
116
140
  const sizeInKb = (output.js.length / 1000).toFixed(2);
117
141
  console.log(`• Bundled component ${componentType} (${sizeInKb} kB)`);
118
142
  }
143
+ for (const [functionName, output] of Object.entries(result.functions)) {
144
+ const sizeInKb = (output.js.length / 1000).toFixed(2);
145
+ console.log(`• Bundled function ${functionName} (${sizeInKb} kB)`);
146
+ }
119
147
  for (const warning of result.warnings) {
120
148
  console.warn(` ⚠️ ${warning.message}`);
121
149
  if (warning.file && process.env.GITHUB_ACTIONS) {