@malloy-publisher/sdk 0.0.17 → 0.0.18

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.
@@ -0,0 +1,220 @@
1
+ import type {
2
+ NotebookStorage,
3
+ UserContext,
4
+ } from "./MutableNotebook/NotebookStorage";
5
+
6
+ /**
7
+ * Interface representing the data structure of a Mutable Notebook
8
+ * @interface NotebookData
9
+ * @property {string[]} models - Array of model paths used in the notebook
10
+ * @property {NotebookCellValue[]} cells - Array of cells in the notebook
11
+ * @property {string} notebookPath - Path to the notebook file (relative to project/package)
12
+ */
13
+ export interface NotebookData {
14
+ models: string[];
15
+ cells: NotebookCellValue[];
16
+ notebookPath: string;
17
+ }
18
+
19
+ /**
20
+ * Interface representing a cell in the notebook
21
+ * @interface NotebookCellValue
22
+ * @property {boolean} isMarkdown - Whether the cell is a markdown cell
23
+ * @property {string} [value] - The content of the cell
24
+ * @property {string} [result] - The result of executing the cell
25
+ * @property {string} [modelPath] - modelPath associated with the query in the cell
26
+ * @property {string} [sourceName] - Name of the source associated with the cell
27
+ * @property {string} [queryInfo] - Information about the query in the cell
28
+ */
29
+ export interface NotebookCellValue {
30
+ isMarkdown: boolean;
31
+ value?: string;
32
+ result?: string;
33
+ modelPath?: string;
34
+ sourceName?: string;
35
+ queryInfo?: string;
36
+ }
37
+
38
+ /**
39
+ * Class for managing notebook operations
40
+ * @class NotebookManager
41
+ */
42
+ export class NotebookManager {
43
+ private isSaved: boolean;
44
+ private notebookStorage: NotebookStorage;
45
+ private userContext: UserContext;
46
+
47
+ /**
48
+ * Creates a new NotebookManager instance
49
+ * @param {NotebookStorage} notebookStorage - Storage implementation
50
+ * @param {UserContext} userContext - User context for storage
51
+ * @param {NotebookData} notebookData - Initial notebook data
52
+ */
53
+ constructor(
54
+ notebookStorage: NotebookStorage,
55
+ userContext: UserContext,
56
+ private notebookData: NotebookData,
57
+ ) {
58
+ this.notebookStorage = notebookStorage;
59
+ this.userContext = userContext;
60
+ if (this.notebookData) {
61
+ this.isSaved = true;
62
+ } else {
63
+ this.notebookData = {
64
+ models: [],
65
+ cells: [],
66
+ notebookPath: undefined,
67
+ };
68
+ this.isSaved = false;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Gets the current notebook data
74
+ * @returns {NotebookData} The current notebook data
75
+ */
76
+ getNotebookData(): NotebookData {
77
+ return this.notebookData;
78
+ }
79
+
80
+ /**
81
+ * Gets the current notebook path
82
+ * @returns {string} The path to the notebook
83
+ */
84
+ getNotebookPath(): string {
85
+ return this.notebookData.notebookPath;
86
+ }
87
+
88
+ /**
89
+ * Renames the notebook and updates storage
90
+ * @param {string} notebookPath - New path for the notebook
91
+ * @returns {NotebookManager} The updated NotebookManager instance
92
+ */
93
+ renameNotebook(notebookPath: string): NotebookManager {
94
+ if (this.notebookData.notebookPath !== notebookPath) {
95
+ try {
96
+ this.notebookStorage.deleteNotebook(
97
+ this.userContext,
98
+ this.notebookData.notebookPath,
99
+ );
100
+ } catch {
101
+ // ignore if not found
102
+ }
103
+ }
104
+ this.notebookData.notebookPath = notebookPath;
105
+ this.isSaved = false;
106
+ this.saveNotebook();
107
+ return this;
108
+ }
109
+
110
+ getCells(): NotebookCellValue[] {
111
+ return this.notebookData.cells;
112
+ }
113
+ deleteCell(index: number): NotebookManager {
114
+ this.notebookData.cells = [
115
+ ...this.notebookData.cells.slice(0, index),
116
+ ...this.notebookData.cells.slice(index + 1),
117
+ ];
118
+ this.isSaved = false;
119
+ return this;
120
+ }
121
+ insertCell(index: number, cell: NotebookCellValue): NotebookManager {
122
+ this.notebookData.cells = [
123
+ ...this.notebookData.cells.slice(0, index),
124
+ cell,
125
+ ...this.notebookData.cells.slice(index),
126
+ ];
127
+ this.isSaved = false;
128
+ return this;
129
+ }
130
+ setCell(index: number, cell: NotebookCellValue): NotebookManager {
131
+ this.notebookData.cells[index] = cell;
132
+ this.isSaved = false;
133
+ return this;
134
+ }
135
+ setModels(models: string[]): NotebookManager {
136
+ this.notebookData.models = models;
137
+ this.isSaved = false;
138
+ return this;
139
+ }
140
+ getModels(): string[] {
141
+ return this.notebookData.models;
142
+ }
143
+
144
+ updateNotebookData(notebookData: NotebookData): NotebookManager {
145
+ this.notebookData = notebookData;
146
+ this.isSaved = false;
147
+ return this;
148
+ }
149
+
150
+ saveNotebook(): NotebookManager {
151
+ if (!this.isSaved) {
152
+ if (!this.notebookData.notebookPath) {
153
+ throw new Error("Notebook path is not set");
154
+ }
155
+ this.notebookStorage.saveNotebook(
156
+ this.userContext,
157
+ this.notebookData.notebookPath,
158
+ JSON.stringify(this.notebookData),
159
+ );
160
+ this.isSaved = true;
161
+ }
162
+ return new NotebookManager(
163
+ this.notebookStorage,
164
+ this.userContext,
165
+ this.notebookData,
166
+ );
167
+ }
168
+
169
+ /**
170
+ * Converts the notebook data to a Malloy notebook string.
171
+ * @returns {string} The Malloy notebook string
172
+ */
173
+ toMalloyNotebook(): string {
174
+ return this.notebookData.cells
175
+ .map((cell) => {
176
+ if (cell.isMarkdown) {
177
+ return ">>>markdown\n" + cell.value;
178
+ } else {
179
+ return (
180
+ ">>>malloy\n" +
181
+ `import "${cell.modelPath}"\n` +
182
+ cell.value +
183
+ "\n"
184
+ );
185
+ }
186
+ })
187
+ .join("\n");
188
+ }
189
+
190
+ static newNotebook(
191
+ notebookStorage: NotebookStorage,
192
+ userContext: UserContext,
193
+ ): NotebookManager {
194
+ return new NotebookManager(notebookStorage, userContext, undefined);
195
+ }
196
+
197
+ /**
198
+ * Creates a new notebook manager by loading from local storage.
199
+ * Returns an empty instance if the notebook is not found.
200
+ * @param notebookStorage - The storage implementation
201
+ * @param userContext - The user context for storage
202
+ * @param notebookPath - The path to the notebook file (relative to project/package)
203
+ */
204
+ static loadNotebook(
205
+ notebookStorage: NotebookStorage,
206
+ userContext: UserContext,
207
+ notebookPath: string,
208
+ ): NotebookManager {
209
+ let notebookData: NotebookData | undefined = undefined;
210
+ try {
211
+ const saved = notebookStorage.getNotebook(userContext, notebookPath);
212
+ if (saved) {
213
+ notebookData = JSON.parse(saved);
214
+ }
215
+ } catch {
216
+ // Not found, return empty
217
+ }
218
+ return new NotebookManager(notebookStorage, userContext, notebookData);
219
+ }
220
+ }
@@ -127,79 +127,129 @@ export default function DatabaseView({
127
127
 
128
128
  function NameAndSchema({ database }: { database: Database }) {
129
129
  const [open, setOpen] = React.useState(false);
130
- return <Box
131
- sx={{ display: 'flex', alignItems: 'center' }}
132
- onClick={() => setOpen(!open)}
133
- style={{ cursor: 'pointer' }}
134
- >
135
-
136
- <Typography
137
- variant="body2"
138
- color="primary"
139
- sx={{
140
- maxWidth: "200px",
141
- overflow: "hidden",
142
- textOverflow: "ellipsis",
143
- whiteSpace: "nowrap",
144
- }}
145
- >
146
- {database.path}
147
- </Typography>
148
- &nbsp;
149
- <Box sx={{ display: 'flex', alignItems: 'center' }}>
150
- <Box sx={{ mr: 1, display: 'flex', alignItems: 'center' }}>
151
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
152
- <path d="M11 7H6C5.46957 7 4.96086 7.21071 4.58579 7.58579C4.21071 7.96086 4 8.46957 4 9V18C4 18.5304 4.21071 19.0391 4.58579 19.4142C4.96086 19.7893 5.46957 20 6 20H15C15.5304 20 16.0391 19.7893 16.4142 19.4142C16.7893 19.0391 17 18.5304 17 18V13" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
153
- <path d="M9 15L20 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
154
- <path d="M15 4H20V9" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
155
- </svg>
156
- </Box>
157
- </Box>
158
- <SchemaButton database={database} open={open} setClose={() => setOpen(false)} />
159
- </Box>
160
- }
161
-
162
- function SchemaButton({ database, open, setClose }: { open: boolean, setClose: () => void, database: Database }) {
163
- return (
164
- <Dialog
165
- open={open}
166
- onClose={setClose}
167
- maxWidth="sm"
168
- fullWidth
130
+ return (
131
+ <Box
132
+ sx={{ display: "flex", alignItems: "center" }}
133
+ onClick={() => setOpen(!open)}
134
+ style={{ cursor: "pointer" }}
169
135
  >
170
- <DialogTitle>
171
- Schema: <Typography fontSize="large" variant="body2" fontFamily="monospace" component="span">{database.path}</Typography>
172
- <IconButton
173
- aria-label="close"
174
- onClick={setClose}
175
- sx={{ position: 'absolute', right: 8, top: 8 }}
136
+ <Typography
137
+ variant="body2"
138
+ color="primary"
139
+ sx={{
140
+ maxWidth: "200px",
141
+ overflow: "hidden",
142
+ textOverflow: "ellipsis",
143
+ whiteSpace: "nowrap",
144
+ }}
176
145
  >
177
- <Box sx={{ width: 24, height: 24, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
178
- X
146
+ {database.path}
147
+ </Typography>
148
+ &nbsp;
149
+ <Box sx={{ display: "flex", alignItems: "center" }}>
150
+ <Box sx={{ mr: 1, display: "flex", alignItems: "center" }}>
151
+ <svg
152
+ width="16"
153
+ height="16"
154
+ viewBox="0 0 24 24"
155
+ fill="none"
156
+ xmlns="http://www.w3.org/2000/svg"
157
+ >
158
+ <path
159
+ d="M11 7H6C5.46957 7 4.96086 7.21071 4.58579 7.58579C4.21071 7.96086 4 8.46957 4 9V18C4 18.5304 4.21071 19.0391 4.58579 19.4142C4.96086 19.7893 5.46957 20 6 20H15C15.5304 20 16.0391 19.7893 16.4142 19.4142C16.7893 19.0391 17 18.5304 17 18V13"
160
+ stroke="currentColor"
161
+ strokeWidth="2"
162
+ strokeLinecap="round"
163
+ strokeLinejoin="round"
164
+ />
165
+ <path
166
+ d="M9 15L20 4"
167
+ stroke="currentColor"
168
+ strokeWidth="2"
169
+ strokeLinecap="round"
170
+ strokeLinejoin="round"
171
+ />
172
+ <path
173
+ d="M15 4H20V9"
174
+ stroke="currentColor"
175
+ strokeWidth="2"
176
+ strokeLinecap="round"
177
+ strokeLinejoin="round"
178
+ />
179
+ </svg>
179
180
  </Box>
180
- </IconButton>
181
- </DialogTitle>
182
- <DialogContent>
183
- <TableContainer>
184
- <Table size="small"
185
- sx={{ "& .MuiTableCell-root": { padding: "10px" } }}>
186
- <TableHead>
187
- <TableRow>
188
- <TableCell>NAME</TableCell>
189
- <TableCell>TYPE</TableCell>
190
- </TableRow>
191
- </TableHead>
192
- <TableBody>
193
- {database.info.columns.map((row) => (
194
- <TableRow key={row.name}>
195
- <TableCell>{row.name}</TableCell>
196
- <TableCell>{row.type}</TableCell>
181
+ </Box>
182
+ <SchemaButton
183
+ database={database}
184
+ open={open}
185
+ setClose={() => setOpen(false)}
186
+ />
187
+ </Box>
188
+ );
189
+ }
190
+
191
+ function SchemaButton({
192
+ database,
193
+ open,
194
+ setClose,
195
+ }: {
196
+ open: boolean;
197
+ setClose: () => void;
198
+ database: Database;
199
+ }) {
200
+ return (
201
+ <Dialog open={open} onClose={setClose} maxWidth="sm" fullWidth>
202
+ <DialogTitle>
203
+ Schema:{" "}
204
+ <Typography
205
+ fontSize="large"
206
+ variant="body2"
207
+ fontFamily="monospace"
208
+ component="span"
209
+ >
210
+ {database.path}
211
+ </Typography>
212
+ <IconButton
213
+ aria-label="close"
214
+ onClick={setClose}
215
+ sx={{ position: "absolute", right: 8, top: 8 }}
216
+ >
217
+ <Box
218
+ sx={{
219
+ width: 24,
220
+ height: 24,
221
+ display: "flex",
222
+ alignItems: "center",
223
+ justifyContent: "center",
224
+ }}
225
+ >
226
+ X
227
+ </Box>
228
+ </IconButton>
229
+ </DialogTitle>
230
+ <DialogContent>
231
+ <TableContainer>
232
+ <Table
233
+ size="small"
234
+ sx={{ "& .MuiTableCell-root": { padding: "10px" } }}
235
+ >
236
+ <TableHead>
237
+ <TableRow>
238
+ <TableCell>NAME</TableCell>
239
+ <TableCell>TYPE</TableCell>
197
240
  </TableRow>
198
- ))}
199
- </TableBody>
200
- </Table>
201
- </TableContainer>
202
- </DialogContent>
241
+ </TableHead>
242
+ <TableBody>
243
+ {database.info.columns.map((row) => (
244
+ <TableRow key={row.name}>
245
+ <TableCell>{row.name}</TableCell>
246
+ <TableCell>{row.type}</TableCell>
247
+ </TableRow>
248
+ ))}
249
+ </TableBody>
250
+ </Table>
251
+ </TableContainer>
252
+ </DialogContent>
203
253
  </Dialog>
204
- )
205
- }
254
+ );
255
+ }
@@ -1,4 +1,4 @@
1
- import { Grid } from "@mui/material";
1
+ import { Button, Grid } from "@mui/material";
2
2
  import { Notebook } from "../Notebook";
3
3
  import Config from "./Config";
4
4
  import Connections from "./Connections";
@@ -83,6 +83,15 @@ export default function Package({
83
83
  accessToken={accessToken}
84
84
  />
85
85
  </Grid>
86
+ <Grid size={{ xs: 12, md: 12 }}>
87
+ <Button
88
+ variant="contained"
89
+ onClick={() => navigate(`list_scratch_notebook`)}
90
+ sx={{ mb: 2 }}
91
+ >
92
+ Scratch Notebooks
93
+ </Button>
94
+ </Grid>
86
95
  <Grid size={{ xs: 12, md: 12 }}>
87
96
  <Notebook
88
97
  server={server}
@@ -0,0 +1,48 @@
1
+ import React, { createContext, useContext, ReactNode } from "react";
2
+
3
+ export interface PublisherPackageContextProps {
4
+ server?: string;
5
+ projectName: string;
6
+ packageName: string;
7
+ versionId?: string;
8
+ accessToken?: string;
9
+ }
10
+
11
+ const PublisherPackageContext = createContext<
12
+ PublisherPackageContextProps | undefined
13
+ >(undefined);
14
+
15
+ interface PublisherPackageProviderProps extends PublisherPackageContextProps {
16
+ children: ReactNode;
17
+ }
18
+
19
+ // Provider for the Publisher Package context.
20
+ // This context is used to pass the package information to the components
21
+ // that need it.
22
+ // The package information is passed to the components via the usePublisherPackage hook.
23
+ export const PublisherPackageProvider = ({
24
+ server,
25
+ projectName,
26
+ packageName,
27
+ versionId,
28
+ accessToken,
29
+ children,
30
+ }: PublisherPackageProviderProps) => {
31
+ return (
32
+ <PublisherPackageContext.Provider
33
+ value={{ server, projectName, packageName, versionId, accessToken }}
34
+ >
35
+ {children}
36
+ </PublisherPackageContext.Provider>
37
+ );
38
+ };
39
+
40
+ export function usePublisherPackage() {
41
+ const context = useContext(PublisherPackageContext);
42
+ if (!context) {
43
+ throw new Error(
44
+ "usePublisherPackage must be used within a PublisherPackageProvider",
45
+ );
46
+ }
47
+ return context;
48
+ }
@@ -1 +1,2 @@
1
1
  export { default as Package } from "./Package";
2
+ export * from "./PublisherPackageProvider";
@@ -77,9 +77,7 @@ export default function QueryResult({
77
77
  )}
78
78
  {isSuccess && (
79
79
  <Suspense fallback="Loading malloy...">
80
- <RenderedResult
81
- result={data.data.result}
82
- />
80
+ <RenderedResult result={data.data.result} />
83
81
  </Suspense>
84
82
  )}
85
83
  {isError && (
@@ -1,5 +1,6 @@
1
1
  export * from "./Model";
2
2
  export * from "./Notebook";
3
+ export * from "./MutableNotebook";
3
4
  export * from "./Package";
4
5
  export * from "./Project";
5
6
  export * from "./QueryResult";
@@ -4,54 +4,52 @@ import dts from "file:///home/kjnesbit/publisher/node_modules/vite-plugin-dts/di
4
4
 
5
5
  // package.json
6
6
  var peerDependencies = {
7
- react: "^18.3.1",
8
- "react-dom": "^18.3.0",
9
- "@emotion/react": "^11.13.3",
10
- "@emotion/styled": "^11.13.0"
7
+ react: "^18.3.1",
8
+ "react-dom": "^18.3.0",
9
+ "@emotion/react": "^11.13.3",
10
+ "@emotion/styled": "^11.13.0",
11
11
  };
12
12
 
13
13
  // vite.config.ts
14
14
  import svgr from "file:///home/kjnesbit/publisher/node_modules/vite-plugin-svgr/dist/index.js";
15
15
  import react from "file:///home/kjnesbit/publisher/node_modules/@vitejs/plugin-react/dist/index.mjs";
16
16
  var vite_config_default = ({ mode }) => {
17
- return defineConfig({
18
- define: {
19
- "process.env": JSON.stringify(mode)
20
- },
21
- build: {
22
- minify: mode === "production",
23
- lib: {
24
- entry: "./src/index.ts",
25
- // Specifies the entry point for building the library.
26
- name: "@malloy-publisher/sdk",
27
- // Sets the name of the generated library.
28
- fileName: (format) => `index.${format}.js`,
29
- // Generates the output file name based on the format.
30
- formats: ["cjs", "es"]
31
- // Specifies the output formats (CommonJS and ES modules).
17
+ return defineConfig({
18
+ define: {
19
+ "process.env": JSON.stringify(mode),
32
20
  },
33
- rollupOptions: {
34
- onwarn(warning, warn) {
35
- if (warning.code === "MODULE_LEVEL_DIRECTIVE") {
36
- return;
37
- }
38
- if (warning.code === "SOURCEMAP_ERROR") {
39
- return;
40
- }
41
- warn(warning);
42
- },
43
- external: [...Object.keys(peerDependencies)]
44
- // Defines external dependencies for Rollup bundling.
21
+ build: {
22
+ minify: mode === "production",
23
+ lib: {
24
+ entry: "./src/index.ts",
25
+ // Specifies the entry point for building the library.
26
+ name: "@malloy-publisher/sdk",
27
+ // Sets the name of the generated library.
28
+ fileName: (format) => `index.${format}.js`,
29
+ // Generates the output file name based on the format.
30
+ formats: ["cjs", "es"],
31
+ // Specifies the output formats (CommonJS and ES modules).
32
+ },
33
+ rollupOptions: {
34
+ onwarn(warning, warn) {
35
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE") {
36
+ return;
37
+ }
38
+ if (warning.code === "SOURCEMAP_ERROR") {
39
+ return;
40
+ }
41
+ warn(warning);
42
+ },
43
+ external: [...Object.keys(peerDependencies)],
44
+ // Defines external dependencies for Rollup bundling.
45
+ },
46
+ sourcemap: true,
47
+ // Generates source maps for debugging.
48
+ emptyOutDir: true,
49
+ // Clears the output directory before building.
45
50
  },
46
- sourcemap: true,
47
- // Generates source maps for debugging.
48
- emptyOutDir: true
49
- // Clears the output directory before building.
50
- },
51
- plugins: [dts(), svgr(), react()]
52
- });
53
- };
54
- export {
55
- vite_config_default as default
51
+ plugins: [dts(), svgr(), react()],
52
+ });
56
53
  };
54
+ export { vite_config_default as default };
57
55
  //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiLCAicGFja2FnZS5qc29uIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL2hvbWUva2puZXNiaXQvcHVibGlzaGVyL3BhY2thZ2VzL3Nka1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL2hvbWUva2puZXNiaXQvcHVibGlzaGVyL3BhY2thZ2VzL3Nkay92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9ram5lc2JpdC9wdWJsaXNoZXIvcGFja2FnZXMvc2RrL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInZpdGVcIjtcbmltcG9ydCBkdHMgZnJvbSBcInZpdGUtcGx1Z2luLWR0c1wiO1xuaW1wb3J0IHsgcGVlckRlcGVuZGVuY2llcyB9IGZyb20gXCIuL3BhY2thZ2UuanNvblwiO1xuaW1wb3J0IHN2Z3IgZnJvbSBcInZpdGUtcGx1Z2luLXN2Z3JcIjtcbmltcG9ydCByZWFjdCBmcm9tIFwiQHZpdGVqcy9wbHVnaW4tcmVhY3RcIjtcblxuZXhwb3J0IGRlZmF1bHQgKHsgbW9kZSB9KSA9PiB7XG4gICByZXR1cm4gZGVmaW5lQ29uZmlnKHtcbiAgICAgIGRlZmluZToge1xuICAgICAgICAgXCJwcm9jZXNzLmVudlwiOiBKU09OLnN0cmluZ2lmeShtb2RlKSxcbiAgICAgIH0sXG4gICAgICBidWlsZDoge1xuICAgICAgICAgbWluaWZ5OiBtb2RlID09PSBcInByb2R1Y3Rpb25cIixcbiAgICAgICAgIGxpYjoge1xuICAgICAgICAgICAgZW50cnk6IFwiLi9zcmMvaW5kZXgudHNcIiwgLy8gU3BlY2lmaWVzIHRoZSBlbnRyeSBwb2ludCBmb3IgYnVpbGRpbmcgdGhlIGxpYnJhcnkuXG4gICAgICAgICAgICBuYW1lOiBcIkBtYWxsb3ktcHVibGlzaGVyL3Nka1wiLCAvLyBTZXRzIHRoZSBuYW1lIG9mIHRoZSBnZW5lcmF0ZWQgbGlicmFyeS5cbiAgICAgICAgICAgIGZpbGVOYW1lOiAoZm9ybWF0KSA9PiBgaW5kZXguJHtmb3JtYXR9LmpzYCwgLy8gR2VuZXJhdGVzIHRoZSBvdXRwdXQgZmlsZSBuYW1lIGJhc2VkIG9uIHRoZSBmb3JtYXQuXG4gICAgICAgICAgICBmb3JtYXRzOiBbXCJjanNcIiwgXCJlc1wiXSwgLy8gU3BlY2lmaWVzIHRoZSBvdXRwdXQgZm9ybWF0cyAoQ29tbW9uSlMgYW5kIEVTIG1vZHVsZXMpLlxuICAgICAgICAgfSxcbiAgICAgICAgIHJvbGx1cE9wdGlvbnM6IHtcbiAgICAgICAgICAgIG9ud2Fybih3YXJuaW5nLCB3YXJuKSB7XG4gICAgICAgICAgICAgICBpZiAod2FybmluZy5jb2RlID09PSBcIk1PRFVMRV9MRVZFTF9ESVJFQ1RJVkVcIikge1xuICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgaWYgKHdhcm5pbmcuY29kZSA9PT0gJ1NPVVJDRU1BUF9FUlJPUicpIHtcbiAgICAgICAgICAgICAgICAgIHJldHVyblxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgIHdhcm4od2FybmluZyk7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgZXh0ZXJuYWw6IFsuLi5PYmplY3Qua2V5cyhwZWVyRGVwZW5kZW5jaWVzKV0sIC8vIERlZmluZXMgZXh0ZXJuYWwgZGVwZW5kZW5jaWVzIGZvciBSb2xsdXAgYnVuZGxpbmcuXG4gICAgICAgICB9LFxuICAgICAgICAgc291cmNlbWFwOiB0cnVlLCAvLyBHZW5lcmF0ZXMgc291cmNlIG1hcHMgZm9yIGRlYnVnZ2luZy5cbiAgICAgICAgIGVtcHR5T3V0RGlyOiB0cnVlLCAvLyBDbGVhcnMgdGhlIG91dHB1dCBkaXJlY3RvcnkgYmVmb3JlIGJ1aWxkaW5nLlxuICAgICAgfSxcbiAgICAgIHBsdWdpbnM6IFtkdHMoKSwgc3ZncigpLCByZWFjdCgpXSxcbiAgIH0pO1xufTtcbiIsICJ7XG4gIFwibmFtZVwiOiBcIkBtYWxsb3ktcHVibGlzaGVyL3Nka1wiLFxuICBcImRlc2NyaXB0aW9uXCI6IFwiTWFsbG95IFB1Ymxpc2hlciBTREtcIixcbiAgXCJ2ZXJzaW9uXCI6IFwiMC4wLjFcIixcbiAgXCJ0eXBlXCI6IFwibW9kdWxlXCIsXG4gIFwibWFpblwiOiBcImRpc3QvaW5kZXguY2pzLmpzXCIsXG4gIFwibW9kdWxlXCI6IFwiZGlzdC9pbmRleC5lcy5qc1wiLFxuICBcInR5cGVzXCI6IFwiZGlzdC9pbmRleC5kLnRzXCIsXG4gIFwic2NyaXB0c1wiOiB7XG4gICAgXCJidWlsZFwiOiBcInRzYyAmJiB2aXRlIGJ1aWxkIC0tbW9kZSAke05PREVfRU5WOj1kZXZlbG9wbWVudH1cIixcbiAgICBcImNsZWFuXCI6IFwicm0gLXJmIC4vZGlzdCAmJiBybSAtcmYgLi9ub2RlX21vZHVsZXMgJiYgcm0gLWYgcGFja2FnZS1sb2NrLmpzb25cIixcbiAgICBcImxpbnRcIjogXCJlc2xpbnQgLi9zcmMgLS1leHQgLnRzLC50c3ggLS1pZ25vcmUtcGF0aCAuZ2l0aWdub3JlIC0tZml4XCIsXG4gICAgXCJmb3JtYXRcIjogXCJwcmV0dGllciAtLXdyaXRlIC0tcGFyc2VyIHR5cGVzY3JpcHQgJyoqLyoue3RzLHRzeH0nXCIsXG4gICAgXCJhbmFseXplXCI6IFwidml0ZS1idW5kbGUtdmlzdWFsaXplclwiLFxuICAgIFwiZ2VuZXJhdGUtYXBpLXR5cGVzXCI6IFwib3BlbmFwaS1nZW5lcmF0b3ItY2xpIGdlbmVyYXRlIC1pIC4uLy4uL2FwaS1kb2MueWFtbCAtZyB0eXBlc2NyaXB0LWF4aW9zIC1vIHNyYy9jbGllbnQvXCJcbiAgfSxcbiAgXCJwZWVyRGVwZW5kZW5jaWVzXCI6IHtcbiAgICBcInJlYWN0XCI6IFwiXjE4LjMuMVwiLFxuICAgIFwicmVhY3QtZG9tXCI6IFwiXjE4LjMuMFwiLFxuICAgIFwiQGVtb3Rpb24vcmVhY3RcIjogXCJeMTEuMTMuM1wiLFxuICAgIFwiQGVtb3Rpb24vc3R5bGVkXCI6IFwiXjExLjEzLjBcIlxuICB9LFxuICBcImRlcGVuZGVuY2llc1wiOiB7XG4gICAgXCJAbWFsbG95ZGF0YS9tYWxsb3lcIjogXCJeMC4wLjIxMVwiLFxuICAgIFwiQG1hbGxveWRhdGEvcmVuZGVyXCI6IFwiXjAuMC4yMTFcIixcbiAgICBcIkBtdWkvaWNvbnMtbWF0ZXJpYWxcIjogXCJeNi4wLjFcIixcbiAgICBcIkBtdWkvbWF0ZXJpYWxcIjogXCJeNi4wLjFcIixcbiAgICBcIkBtdWkveC10cmVlLXZpZXdcIjogXCJeNy4xNi4wXCIsXG4gICAgXCJAcmVhY3Qtc3ByaW5nL3dlYlwiOiBcIl45LjcuNFwiLFxuICAgIFwiQHZpdGVqcy9wbHVnaW4tcmVhY3RcIjogXCJeNC4zLjFcIixcbiAgICBcImF4aW9zXCI6IFwiXjEuNy43XCIsXG4gICAgXCJtYXJrZG93bi10by1qc3hcIjogXCJeNy41LjBcIixcbiAgICBcIm1kYXN0LWNvbW1lbnQtbWFya2VyXCI6IFwiXjIuMS4yXCIsXG4gICAgXCJ0eXBlc2NyaXB0XCI6IFwiNC43LjRcIixcbiAgICBcInVuaWZpZWRcIjogXCJeMTEuMC41XCIsXG4gICAgXCJ2aXRlXCI6IFwiXjUuMy41XCIsXG4gICAgXCJ2aXRlLXBsdWdpbi1kdHNcIjogXCJeNC4wLjBcIixcbiAgICBcInZpdGUtcGx1Z2luLXN2Z3JcIjogXCJeNC4yLjBcIixcbiAgICBcIkB0YW5zdGFjay9yZWFjdC1xdWVyeVwiOiBcIl41LjU5LjE2XCJcbiAgfSxcbiAgXCJkZXZEZXBlbmRlbmNpZXNcIjoge1xuICAgIFwiQG9wZW5hcGl0b29scy9vcGVuYXBpLWdlbmVyYXRvci1jbGlcIjogXCJeMi4xMy41XCIsXG4gICAgXCJAdHlwZXMvcmVhY3RcIjogXCJeMTguMy4xXCIsXG4gICAgXCJAdHlwZXNjcmlwdC1lc2xpbnQvZXNsaW50LXBsdWdpblwiOiBcIl44LjAuMVwiLFxuICAgIFwiQHR5cGVzY3JpcHQtZXNsaW50L3BhcnNlclwiOiBcIl44LjAuMVwiLFxuICAgIFwiZXNsaW50XCI6IFwiXjguNTcuMFwiLFxuICAgIFwiZXNsaW50LWNvbmZpZy1wcmV0dGllclwiOiBcIl45LjEuMFwiLFxuICAgIFwiZXNsaW50LXBsdWdpbi1wcmV0dGllclwiOiBcIl41LjIuMVwiLFxuICAgIFwiZXNsaW50LXBsdWdpbi1yZWFjdFwiOiBcIl43LjM1LjBcIixcbiAgICBcImVzbGludC1wbHVnaW4tcmVhY3QtaG9va3NcIjogXCJeNC42LjJcIixcbiAgICBcImVzbGludC1wbHVnaW4tc3Rvcnlib29rXCI6IFwiXjAuOC4wXCIsXG4gICAgXCJvcGVuYXBpLXR5cGVzY3JpcHRcIjogXCJeNi43LjZcIixcbiAgICBcInByZXR0aWVyXCI6IFwiXjMuMy4zXCIsXG4gICAgXCJzaGlraVwiOiBcIl4xLjE2LjNcIixcbiAgICBcInN0eWxlZC1jb21wb25lbnRzXCI6IFwiXjYuMS4xMlwiLFxuICAgIFwidHlwZXNjcmlwdFwiOiBcIl41LjUuNFwiLFxuICAgIFwidml0ZS1idW5kbGUtdmlzdWFsaXplclwiOiBcIl4xLjIuMVwiXG4gIH1cbn1cbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBaVMsU0FBUyxvQkFBb0I7QUFDOVQsT0FBTyxTQUFTOzs7QUNlZCx1QkFBb0I7QUFBQSxFQUNsQixPQUFTO0FBQUEsRUFDVCxhQUFhO0FBQUEsRUFDYixrQkFBa0I7QUFBQSxFQUNsQixtQkFBbUI7QUFDckI7OztBRGxCRixPQUFPLFVBQVU7QUFDakIsT0FBTyxXQUFXO0FBRWxCLElBQU8sc0JBQVEsQ0FBQyxFQUFFLEtBQUssTUFBTTtBQUMxQixTQUFPLGFBQWE7QUFBQSxJQUNqQixRQUFRO0FBQUEsTUFDTCxlQUFlLEtBQUssVUFBVSxJQUFJO0FBQUEsSUFDckM7QUFBQSxJQUNBLE9BQU87QUFBQSxNQUNKLFFBQVEsU0FBUztBQUFBLE1BQ2pCLEtBQUs7QUFBQSxRQUNGLE9BQU87QUFBQTtBQUFBLFFBQ1AsTUFBTTtBQUFBO0FBQUEsUUFDTixVQUFVLENBQUMsV0FBVyxTQUFTLE1BQU07QUFBQTtBQUFBLFFBQ3JDLFNBQVMsQ0FBQyxPQUFPLElBQUk7QUFBQTtBQUFBLE1BQ3hCO0FBQUEsTUFDQSxlQUFlO0FBQUEsUUFDWixPQUFPLFNBQVMsTUFBTTtBQUNuQixjQUFJLFFBQVEsU0FBUywwQkFBMEI7QUFDNUM7QUFBQSxVQUNIO0FBQ0EsY0FBSSxRQUFRLFNBQVMsbUJBQW1CO0FBQ3JDO0FBQUEsVUFDRjtBQUNELGVBQUssT0FBTztBQUFBLFFBQ2Y7QUFBQSxRQUNBLFVBQVUsQ0FBQyxHQUFHLE9BQU8sS0FBSyxnQkFBZ0IsQ0FBQztBQUFBO0FBQUEsTUFDOUM7QUFBQSxNQUNBLFdBQVc7QUFBQTtBQUFBLE1BQ1gsYUFBYTtBQUFBO0FBQUEsSUFDaEI7QUFBQSxJQUNBLFNBQVMsQ0FBQyxJQUFJLEdBQUcsS0FBSyxHQUFHLE1BQU0sQ0FBQztBQUFBLEVBQ25DLENBQUM7QUFDSjsiLAogICJuYW1lcyI6IFtdCn0K