@dbx-tools/appkit-generator 0.0.0-stage → 0.9.138
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/LICENSE +202 -0
- package/README.md +181 -2
- package/index.ts +44 -0
- package/lib/index.d.ts +40 -0
- package/lib/index.js +30 -0
- package/lib/src/assets.d.ts +33 -0
- package/lib/src/assets.js +224 -0
- package/lib/src/config.d.ts +75 -0
- package/lib/src/config.js +31 -0
- package/lib/src/dashboard.d.ts +30 -0
- package/lib/src/dashboard.js +257 -0
- package/lib/src/data.d.ts +17 -0
- package/lib/src/data.js +114 -0
- package/lib/src/defaults.d.ts +39 -0
- package/lib/src/defaults.js +36 -0
- package/lib/src/generate.d.ts +51 -0
- package/lib/src/generate.js +163 -0
- package/lib/src/genie.d.ts +51 -0
- package/lib/src/genie.js +84 -0
- package/lib/src/kpi.d.ts +22 -0
- package/lib/src/kpi.js +74 -0
- package/lib/src/manifest.json +72 -0
- package/lib/src/namespace.d.ts +36 -0
- package/lib/src/namespace.js +119 -0
- package/lib/src/operations.d.ts +42 -0
- package/lib/src/operations.js +96 -0
- package/lib/src/palette.d.ts +10 -0
- package/lib/src/palette.js +69 -0
- package/lib/src/plan.d.ts +21 -0
- package/lib/src/plan.js +33 -0
- package/lib/src/plugin.d.ts +619 -0
- package/lib/src/plugin.js +205 -0
- package/lib/src/research.d.ts +78 -0
- package/lib/src/research.js +235 -0
- package/lib/src/schema.d.ts +52 -0
- package/lib/src/schema.js +82 -0
- package/lib/src/structured.d.ts +27 -0
- package/lib/src/structured.js +30 -0
- package/lib/src/workflow.d.ts +314 -0
- package/lib/src/workflow.js +171 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +218 -4
- package/src/assets.ts +248 -0
- package/src/config.ts +98 -0
- package/src/dashboard.ts +295 -0
- package/src/data.ts +167 -0
- package/src/defaults.ts +61 -0
- package/src/generate.ts +250 -0
- package/src/genie.ts +111 -0
- package/src/kpi.ts +106 -0
- package/src/manifest.json +72 -0
- package/src/namespace.ts +192 -0
- package/src/operations.ts +171 -0
- package/src/palette.ts +81 -0
- package/src/plan.ts +59 -0
- package/src/plugin.ts +244 -0
- package/src/research.ts +382 -0
- package/src/schema.ts +114 -0
- package/src/structured.ts +54 -0
- package/src/workflow.ts +221 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mastra workflow composing reusable generator operations.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { BrandPackageSchema } from "@dbx-tools/shared-generator/brand";
|
|
7
|
+
import { KpiResearchResultSchema } from "@dbx-tools/shared-generator/kpi";
|
|
8
|
+
import { GeneratedAssetsSchema, GeneratedSchemaSchema, GeneratorPlanSchema, GeneratorRequestSchema, MaterializedGeneratorDataSchema, } from "@dbx-tools/shared-generator/workflow";
|
|
9
|
+
import { createStep, createWorkflow } from "@mastra/core/workflows";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
const ResearchOutputSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
request: GeneratorRequestSchema,
|
|
14
|
+
brand: BrandPackageSchema,
|
|
15
|
+
kpiResearch: KpiResearchResultSchema,
|
|
16
|
+
})
|
|
17
|
+
.strict();
|
|
18
|
+
const PlanOutputSchema = ResearchOutputSchema.extend({
|
|
19
|
+
plan: GeneratorPlanSchema,
|
|
20
|
+
}).strict();
|
|
21
|
+
const ProvisionOutputSchema = PlanOutputSchema.extend({
|
|
22
|
+
location: GeneratedSchemaSchema,
|
|
23
|
+
}).strict();
|
|
24
|
+
const MaterializeOutputSchema = ProvisionOutputSchema.extend({
|
|
25
|
+
materialized: MaterializedGeneratorDataSchema,
|
|
26
|
+
}).strict();
|
|
27
|
+
/** Build one typed workflow over an isolated generator operation owner. */
|
|
28
|
+
export function createGeneratorWorkflow(operations, options = {}) {
|
|
29
|
+
const research = createStep({
|
|
30
|
+
id: "research-brand-and-kpis",
|
|
31
|
+
inputSchema: GeneratorRequestSchema,
|
|
32
|
+
outputSchema: ResearchOutputSchema,
|
|
33
|
+
execute: async ({ inputData, abortSignal, writer }) => {
|
|
34
|
+
await writer?.write({ type: "generator-progress", stage: "research", status: "started" });
|
|
35
|
+
const brandRequest = {
|
|
36
|
+
name: inputData.company,
|
|
37
|
+
...(inputData.website ? { website: inputData.website } : {}),
|
|
38
|
+
};
|
|
39
|
+
const [brand, kpiResearch] = await Promise.all([
|
|
40
|
+
operations.generateBrand(brandRequest, abortSignal),
|
|
41
|
+
operations.researchKpis({
|
|
42
|
+
company: inputData.company,
|
|
43
|
+
purpose: inputData.purpose,
|
|
44
|
+
queries: inputData.queries,
|
|
45
|
+
}, abortSignal),
|
|
46
|
+
]);
|
|
47
|
+
return { request: inputData, brand, kpiResearch };
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const plan = createStep({
|
|
51
|
+
id: "plan-demo-data",
|
|
52
|
+
inputSchema: ResearchOutputSchema,
|
|
53
|
+
outputSchema: PlanOutputSchema,
|
|
54
|
+
execute: async ({ inputData, abortSignal, writer }) => {
|
|
55
|
+
await writer?.write({ type: "generator-progress", stage: "plan", status: "started" });
|
|
56
|
+
const generatedPlan = await operations.planData(inputData, abortSignal);
|
|
57
|
+
return { ...inputData, plan: generatedPlan };
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const approval = createStep({
|
|
61
|
+
id: "approve-mutation-plan",
|
|
62
|
+
inputSchema: PlanOutputSchema,
|
|
63
|
+
outputSchema: PlanOutputSchema,
|
|
64
|
+
resumeSchema: z.object({
|
|
65
|
+
approved: z.boolean().describe("Whether the generated mutation plan is approved."),
|
|
66
|
+
}),
|
|
67
|
+
suspendSchema: z.object({
|
|
68
|
+
reason: z.string().describe("Why approval is required."),
|
|
69
|
+
plan: GeneratorPlanSchema,
|
|
70
|
+
}),
|
|
71
|
+
execute: async ({ inputData, resumeData, suspend }) => {
|
|
72
|
+
if (options.approval === false)
|
|
73
|
+
return inputData;
|
|
74
|
+
if (resumeData === undefined) {
|
|
75
|
+
return suspend({
|
|
76
|
+
reason: "Approve schema, data, and presentation creation.",
|
|
77
|
+
plan: inputData.plan,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (!resumeData.approved) {
|
|
81
|
+
throw new Error("Generator mutation plan was rejected");
|
|
82
|
+
}
|
|
83
|
+
return inputData;
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
const provision = createStep({
|
|
87
|
+
id: "provision-schema",
|
|
88
|
+
inputSchema: PlanOutputSchema,
|
|
89
|
+
outputSchema: ProvisionOutputSchema,
|
|
90
|
+
execute: async ({ inputData, abortSignal, writer }) => {
|
|
91
|
+
await writer?.write({ type: "generator-progress", stage: "schema", status: "started" });
|
|
92
|
+
const location = await operations.provisionSchema({
|
|
93
|
+
request: {
|
|
94
|
+
company: inputData.request.company,
|
|
95
|
+
purpose: inputData.request.purpose,
|
|
96
|
+
},
|
|
97
|
+
}, abortSignal);
|
|
98
|
+
return { ...inputData, location };
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const materialize = createStep({
|
|
102
|
+
id: "materialize-and-validate-data",
|
|
103
|
+
inputSchema: ProvisionOutputSchema,
|
|
104
|
+
outputSchema: MaterializeOutputSchema,
|
|
105
|
+
execute: async ({ inputData, abortSignal, writer }) => {
|
|
106
|
+
await writer?.write({ type: "generator-progress", stage: "data", status: "started" });
|
|
107
|
+
const materialized = await operations.materializeData({
|
|
108
|
+
location: inputData.location,
|
|
109
|
+
plan: inputData.plan,
|
|
110
|
+
}, abortSignal);
|
|
111
|
+
return { ...inputData, materialized };
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const presentation = createStep({
|
|
115
|
+
id: "create-presentation",
|
|
116
|
+
inputSchema: MaterializeOutputSchema,
|
|
117
|
+
outputSchema: GeneratedAssetsSchema,
|
|
118
|
+
execute: async ({ inputData, abortSignal, writer }) => {
|
|
119
|
+
await writer?.write({
|
|
120
|
+
type: "generator-progress",
|
|
121
|
+
stage: "presentation",
|
|
122
|
+
status: "started",
|
|
123
|
+
});
|
|
124
|
+
const presentationInput = {
|
|
125
|
+
request: {
|
|
126
|
+
company: inputData.request.company,
|
|
127
|
+
purpose: inputData.request.purpose,
|
|
128
|
+
},
|
|
129
|
+
location: inputData.location,
|
|
130
|
+
plan: inputData.plan,
|
|
131
|
+
brand: inputData.brand,
|
|
132
|
+
};
|
|
133
|
+
if (inputData.request.presentation === "dashboard") {
|
|
134
|
+
const dashboard = await operations.createDashboard(presentationInput, abortSignal);
|
|
135
|
+
return GeneratedAssetsSchema.parse({
|
|
136
|
+
request: inputData.request,
|
|
137
|
+
schema: inputData.location,
|
|
138
|
+
tables: inputData.materialized.tables,
|
|
139
|
+
brand: inputData.brand,
|
|
140
|
+
kpiResearch: inputData.kpiResearch,
|
|
141
|
+
plan: inputData.plan,
|
|
142
|
+
dashboard,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const genie = await operations.createGenie(presentationInput, abortSignal);
|
|
146
|
+
return GeneratedAssetsSchema.parse({
|
|
147
|
+
request: inputData.request,
|
|
148
|
+
schema: inputData.location,
|
|
149
|
+
tables: inputData.materialized.tables,
|
|
150
|
+
brand: inputData.brand,
|
|
151
|
+
kpiResearch: inputData.kpiResearch,
|
|
152
|
+
plan: inputData.plan,
|
|
153
|
+
genie,
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
return createWorkflow({
|
|
158
|
+
id: "generator-demo-assets",
|
|
159
|
+
inputSchema: GeneratorRequestSchema,
|
|
160
|
+
outputSchema: GeneratedAssetsSchema,
|
|
161
|
+
options: { autoRestartActiveRuns: false },
|
|
162
|
+
})
|
|
163
|
+
.then(research)
|
|
164
|
+
.then(plan)
|
|
165
|
+
.then(approval)
|
|
166
|
+
.then(provision)
|
|
167
|
+
.then(materialize)
|
|
168
|
+
.then(presentation)
|
|
169
|
+
.commit();
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya2Zsb3cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvd29ya2Zsb3cudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7R0FJRztBQUVILE9BQU8sRUFBRSxrQkFBa0IsRUFBK0IsTUFBTSxtQ0FBbUMsQ0FBQztBQUNwRyxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUMxRSxPQUFPLEVBQ0wscUJBQXFCLEVBQ3JCLHFCQUFxQixFQUNyQixtQkFBbUIsRUFDbkIsc0JBQXNCLEVBQ3RCLCtCQUErQixHQUNoQyxNQUFNLHNDQUFzQyxDQUFDO0FBQzlDLE9BQU8sRUFBRSxVQUFVLEVBQUUsY0FBYyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDcEUsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQztBQXFCeEIsTUFBTSxvQkFBb0IsR0FBRyxDQUFDO0tBQzNCLE1BQU0sQ0FBQztJQUNOLE9BQU8sRUFBRSxzQkFBc0I7SUFDL0IsS0FBSyxFQUFFLGtCQUFrQjtJQUN6QixXQUFXLEVBQUUsdUJBQXVCO0NBQ3JDLENBQUM7S0FDRCxNQUFNLEVBQUUsQ0FBQztBQUVaLE1BQU0sZ0JBQWdCLEdBQUcsb0JBQW9CLENBQUMsTUFBTSxDQUFDO0lBQ25ELElBQUksRUFBRSxtQkFBbUI7Q0FDMUIsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDO0FBRVosTUFBTSxxQkFBcUIsR0FBRyxnQkFBZ0IsQ0FBQyxNQUFNLENBQUM7SUFDcEQsUUFBUSxFQUFFLHFCQUFxQjtDQUNoQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUM7QUFFWixNQUFNLHVCQUF1QixHQUFHLHFCQUFxQixDQUFDLE1BQU0sQ0FBQztJQUMzRCxZQUFZLEVBQUUsK0JBQStCO0NBQzlDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQztBQUVaLDJFQUEyRTtBQUMzRSxNQUFNLFVBQVUsdUJBQXVCLENBQ3JDLFVBQXVDLEVBQ3ZDLFVBQW9DLEVBQUU7SUFFdEMsTUFBTSxRQUFRLEdBQUcsVUFBVSxDQUFDO1FBQzFCLEVBQUUsRUFBRSx5QkFBeUI7UUFDN0IsV0FBVyxFQUFFLHNCQUFzQjtRQUNuQyxZQUFZLEVBQUUsb0JBQW9CO1FBQ2xDLE9BQU8sRUFBRSxLQUFLLEVBQUUsRUFBRSxTQUFTLEVBQUUsV0FBVyxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUU7WUFDcEQsTUFBTSxNQUFNLEVBQUUsS0FBSyxDQUFDLEVBQUUsSUFBSSxFQUFFLG9CQUFvQixFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUM7WUFDMUYsTUFBTSxZQUFZLEdBQTJCO2dCQUMzQyxJQUFJLEVBQUUsU0FBUyxDQUFDLE9BQU87Z0JBQ3ZCLEdBQUcsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLE9BQU8sRUFBRSxTQUFTLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQzthQUM3RCxDQUFDO1lBQ0YsTUFBTSxDQUFDLEtBQUssRUFBRSxXQUFXLENBQUMsR0FBRyxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUM7Z0JBQzdDLFVBQVUsQ0FBQyxhQUFhLENBQUMsWUFBWSxFQUFFLFdBQVcsQ0FBQztnQkFDbkQsVUFBVSxDQUFDLFlBQVksQ0FDckI7b0JBQ0UsT0FBTyxFQUFFLFNBQVMsQ0FBQyxPQUFPO29CQUMxQixPQUFPLEVBQUUsU0FBUyxDQUFDLE9BQU87b0JBQzFCLE9BQU8sRUFBRSxTQUFTLENBQUMsT0FBTztpQkFDM0IsRUFDRCxXQUFXLENBQ1o7YUFDRixDQUFDLENBQUM7WUFDSCxPQUFPLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsV0FBVyxFQUFFLENBQUM7UUFDcEQsQ0FBQztLQUNGLENBQUMsQ0FBQztJQUVILE1BQU0sSUFBSSxHQUFHLFVBQVUsQ0FBQztRQUN0QixFQUFFLEVBQUUsZ0JBQWdCO1FBQ3BCLFdBQVcsRUFBRSxvQkFBb0I7UUFDakMsWUFBWSxFQUFFLGdCQUFnQjtRQUM5QixPQUFPLEVBQUUsS0FBSyxFQUFFLEVBQUUsU0FBUyxFQUFFLFdBQVcsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFO1lBQ3BELE1BQU0sTUFBTSxFQUFFLEtBQUssQ0FBQyxFQUFFLElBQUksRUFBRSxvQkFBb0IsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO1lBQ3RGLE1BQU0sYUFBYSxHQUFHLE1BQU0sVUFBVSxDQUFDLFFBQVEsQ0FBQyxTQUFTLEVBQUUsV0FBVyxDQUFDLENBQUM7WUFDeEUsT0FBTyxFQUFFLEdBQUcsU0FBUyxFQUFFLElBQUksRUFBRSxhQUFhLEVBQUUsQ0FBQztRQUMvQyxDQUFDO0tBQ0YsQ0FBQyxDQUFDO0lBRUgsTUFBTSxRQUFRLEdBQUcsVUFBVSxDQUFDO1FBQzFCLEVBQUUsRUFBRSx1QkFBdUI7UUFDM0IsV0FBVyxFQUFFLGdCQUFnQjtRQUM3QixZQUFZLEVBQUUsZ0JBQWdCO1FBQzlCLFlBQVksRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDO1lBQ3JCLFFBQVEsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxDQUFDLGtEQUFrRCxDQUFDO1NBQ25GLENBQUM7UUFDRixhQUFhLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQztZQUN0QixNQUFNLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsQ0FBQywyQkFBMkIsQ0FBQztZQUN4RCxJQUFJLEVBQUUsbUJBQW1CO1NBQzFCLENBQUM7UUFDRixPQUFPLEVBQUUsS0FBSyxFQUFFLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxPQUFPLEVBQUUsRUFBRSxFQUFFO1lBQ3BELElBQUksT0FBTyxDQUFDLFFBQVEsS0FBSyxLQUFLO2dCQUFFLE9BQU8sU0FBUyxDQUFDO1lBQ2pELElBQUksVUFBVSxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUM3QixPQUFPLE9BQU8sQ0FBQztvQkFDYixNQUFNLEVBQUUsa0RBQWtEO29CQUMxRCxJQUFJLEVBQUUsU0FBUyxDQUFDLElBQUk7aUJBQ3JCLENBQUMsQ0FBQztZQUNMLENBQUM7WUFDRCxJQUFJLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRSxDQUFDO2dCQUN6QixNQUFNLElBQUksS0FBSyxDQUFDLHNDQUFzQyxDQUFDLENBQUM7WUFDMUQsQ0FBQztZQUNELE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7S0FDRixDQUFDLENBQUM7SUFFSCxNQUFNLFNBQVMsR0FBRyxVQUFVLENBQUM7UUFDM0IsRUFBRSxFQUFFLGtCQUFrQjtRQUN0QixXQUFXLEVBQUUsZ0JBQWdCO1FBQzdCLFlBQVksRUFBRSxxQkFBcUI7UUFDbkMsT0FBTyxFQUFFLEtBQUssRUFBRSxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRTtZQUNwRCxNQUFNLE1BQU0sRUFBRSxLQUFLLENBQUMsRUFBRSxJQUFJLEVBQUUsb0JBQW9CLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQztZQUN4RixNQUFNLFFBQVEsR0FBRyxNQUFNLFVBQVUsQ0FBQyxlQUFlLENBQy9DO2dCQUNFLE9BQU8sRUFBRTtvQkFDUCxPQUFPLEVBQUUsU0FBUyxDQUFDLE9BQU8sQ0FBQyxPQUFPO29CQUNsQyxPQUFPLEVBQUUsU0FBUyxDQUFDLE9BQU8sQ0FBQyxPQUFPO2lCQUNuQzthQUNGLEVBQ0QsV0FBVyxDQUNaLENBQUM7WUFDRixPQUFPLEVBQUUsR0FBRyxTQUFTLEVBQUUsUUFBUSxFQUFFLENBQUM7UUFDcEMsQ0FBQztLQUNGLENBQUMsQ0FBQztJQUVILE1BQU0sV0FBVyxHQUFHLFVBQVUsQ0FBQztRQUM3QixFQUFFLEVBQUUsK0JBQStCO1FBQ25DLFdBQVcsRUFBRSxxQkFBcUI7UUFDbEMsWUFBWSxFQUFFLHVCQUF1QjtRQUNyQyxPQUFPLEVBQUUsS0FBSyxFQUFFLEVBQUUsU0FBUyxFQUFFLFdBQVcsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFO1lBQ3BELE1BQU0sTUFBTSxFQUFFLEtBQUssQ0FBQyxFQUFFLElBQUksRUFBRSxvQkFBb0IsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO1lBQ3RGLE1BQU0sWUFBWSxHQUFHLE1BQU0sVUFBVSxDQUFDLGVBQWUsQ0FDbkQ7Z0JBQ0UsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO2dCQUM1QixJQUFJLEVBQUUsU0FBUyxDQUFDLElBQUk7YUFDckIsRUFDRCxXQUFXLENBQ1osQ0FBQztZQUNGLE9BQU8sRUFBRSxHQUFHLFNBQVMsRUFBRSxZQUFZLEVBQUUsQ0FBQztRQUN4QyxDQUFDO0tBQ0YsQ0FBQyxDQUFDO0lBRUgsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFDO1FBQzlCLEVBQUUsRUFBRSxxQkFBcUI7UUFDekIsV0FBVyxFQUFFLHVCQUF1QjtRQUNwQyxZQUFZLEVBQUUscUJBQXFCO1FBQ25DLE9BQU8sRUFBRSxLQUFLLEVBQUUsRUFBRSxTQUFTLEVBQUUsV0FBVyxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUU7WUFDcEQsTUFBTSxNQUFNLEVBQUUsS0FBSyxDQUFDO2dCQUNsQixJQUFJLEVBQUUsb0JBQW9CO2dCQUMxQixLQUFLLEVBQUUsY0FBYztnQkFDckIsTUFBTSxFQUFFLFNBQVM7YUFDbEIsQ0FBQyxDQUFDO1lBQ0gsTUFBTSxpQkFBaUIsR0FBRztnQkFDeEIsT0FBTyxFQUFFO29CQUNQLE9BQU8sRUFBRSxTQUFTLENBQUMsT0FBTyxDQUFDLE9BQU87b0JBQ2xDLE9BQU8sRUFBRSxTQUFTLENBQUMsT0FBTyxDQUFDLE9BQU87aUJBQ25DO2dCQUNELFFBQVEsRUFBRSxTQUFTLENBQUMsUUFBUTtnQkFDNUIsSUFBSSxFQUFFLFNBQVMsQ0FBQyxJQUFJO2dCQUNwQixLQUFLLEVBQUUsU0FBUyxDQUFDLEtBQUs7YUFDdkIsQ0FBQztZQUNGLElBQUksU0FBUyxDQUFDLE9BQU8sQ0FBQyxZQUFZLEtBQUssV0FBVyxFQUFFLENBQUM7Z0JBQ25ELE1BQU0sU0FBUyxHQUFHLE1BQU0sVUFBVSxDQUFDLGVBQWUsQ0FBQyxpQkFBaUIsRUFBRSxXQUFXLENBQUMsQ0FBQztnQkFDbkYsT0FBTyxxQkFBcUIsQ0FBQyxLQUFLLENBQUM7b0JBQ2pDLE9BQU8sRUFBRSxTQUFTLENBQUMsT0FBTztvQkFDMUIsTUFBTSxFQUFFLFNBQVMsQ0FBQyxRQUFRO29CQUMxQixNQUFNLEVBQUUsU0FBUyxDQUFDLFlBQVksQ0FBQyxNQUFNO29CQUNyQyxLQUFLLEVBQUUsU0FBUyxDQUFDLEtBQUs7b0JBQ3RCLFdBQVcsRUFBRSxTQUFTLENBQUMsV0FBVztvQkFDbEMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxJQUFJO29CQUNwQixTQUFTO2lCQUNWLENBQUMsQ0FBQztZQUNMLENBQUM7WUFDRCxNQUFNLEtBQUssR0FBRyxNQUFNLFVBQVUsQ0FBQyxXQUFXLENBQUMsaUJBQWlCLEVBQUUsV0FBVyxDQUFDLENBQUM7WUFDM0UsT0FBTyxxQkFBcUIsQ0FBQyxLQUFLLENBQUM7Z0JBQ2pDLE9BQU8sRUFBRSxTQUFTLENBQUMsT0FBTztnQkFDMUIsTUFBTSxFQUFFLFNBQVMsQ0FBQyxRQUFRO2dCQUMxQixNQUFNLEVBQUUsU0FBUyxDQUFDLFlBQVksQ0FBQyxNQUFNO2dCQUNyQyxLQUFLLEVBQUUsU0FBUyxDQUFDLEtBQUs7Z0JBQ3RCLFdBQVcsRUFBRSxTQUFTLENBQUMsV0FBVztnQkFDbEMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxJQUFJO2dCQUNwQixLQUFLO2FBQ04sQ0FBQyxDQUFDO1FBQ0wsQ0FBQztLQUNGLENBQUMsQ0FBQztJQUVILE9BQU8sY0FBYyxDQUFDO1FBQ3BCLEVBQUUsRUFBRSx1QkFBdUI7UUFDM0IsV0FBVyxFQUFFLHNCQUFzQjtRQUNuQyxZQUFZLEVBQUUscUJBQXFCO1FBQ25DLE9BQU8sRUFBRSxFQUFFLHFCQUFxQixFQUFFLEtBQUssRUFBRTtLQUMxQyxDQUFDO1NBQ0MsSUFBSSxDQUFDLFFBQVEsQ0FBQztTQUNkLElBQUksQ0FBQyxJQUFJLENBQUM7U0FDVixJQUFJLENBQUMsUUFBUSxDQUFDO1NBQ2QsSUFBSSxDQUFDLFNBQVMsQ0FBQztTQUNmLElBQUksQ0FBQyxXQUFXLENBQUM7U0FDakIsSUFBSSxDQUFDLFlBQVksQ0FBQztTQUNsQixNQUFNLEVBQUUsQ0FBQztBQUNkLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIE1hc3RyYSB3b3JrZmxvdyBjb21wb3NpbmcgcmV1c2FibGUgZ2VuZXJhdG9yIG9wZXJhdGlvbnMuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IEJyYW5kUGFja2FnZVNjaGVtYSwgdHlwZSBCcmFuZEdlbmVyYXRpb25SZXF1ZXN0IH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWdlbmVyYXRvci9icmFuZFwiO1xuaW1wb3J0IHsgS3BpUmVzZWFyY2hSZXN1bHRTY2hlbWEgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtZ2VuZXJhdG9yL2twaVwiO1xuaW1wb3J0IHtcbiAgR2VuZXJhdGVkQXNzZXRzU2NoZW1hLFxuICBHZW5lcmF0ZWRTY2hlbWFTY2hlbWEsXG4gIEdlbmVyYXRvclBsYW5TY2hlbWEsXG4gIEdlbmVyYXRvclJlcXVlc3RTY2hlbWEsXG4gIE1hdGVyaWFsaXplZEdlbmVyYXRvckRhdGFTY2hlbWEsXG59IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1nZW5lcmF0b3Ivd29ya2Zsb3dcIjtcbmltcG9ydCB7IGNyZWF0ZVN0ZXAsIGNyZWF0ZVdvcmtmbG93IH0gZnJvbSBcIkBtYXN0cmEvY29yZS93b3JrZmxvd3NcIjtcbmltcG9ydCB7IHogfSBmcm9tIFwiem9kXCI7XG5pbXBvcnQgdHlwZSB7IEdlbmVyYXRvck9wZXJhdGlvbnMgfSBmcm9tIFwiLi9vcGVyYXRpb25zLnRzXCI7XG5cbi8qKiBPcGVyYXRpb24gc3VyZmFjZSBjb25zdW1lZCBieSB0aGUgd29ya2Zsb3cgYW5kIGltcGxlbWVudGVkIGJ5IEdlbmVyYXRvck9wZXJhdGlvbnMuICovXG5leHBvcnQgdHlwZSBHZW5lcmF0b3JXb3JrZmxvd09wZXJhdGlvbnMgPSBQaWNrPFxuICBHZW5lcmF0b3JPcGVyYXRpb25zLFxuICB8IFwiZ2VuZXJhdGVCcmFuZFwiXG4gIHwgXCJyZXNlYXJjaEtwaXNcIlxuICB8IFwicGxhbkRhdGFcIlxuICB8IFwicHJvdmlzaW9uU2NoZW1hXCJcbiAgfCBcIm1hdGVyaWFsaXplRGF0YVwiXG4gIHwgXCJjcmVhdGVEYXNoYm9hcmRcIlxuICB8IFwiY3JlYXRlR2VuaWVcIlxuPjtcblxuLyoqIENvbnN0cnVjdGlvbiBvcHRpb25zIGZvciB3b3JrZmxvdyBob3N0cyBhbmQgaXNvbGF0ZWQgdGVzdHMuICovXG5leHBvcnQgaW50ZXJmYWNlIEdlbmVyYXRvcldvcmtmbG93T3B0aW9ucyB7XG4gIC8qKiBTdXNwZW5kIGJlZm9yZSBtdXRhdGlvbnMuIERlZmF1bHRzIHRvIHRydWUuICovXG4gIGFwcHJvdmFsPzogYm9vbGVhbjtcbn1cblxuY29uc3QgUmVzZWFyY2hPdXRwdXRTY2hlbWEgPSB6XG4gIC5vYmplY3Qoe1xuICAgIHJlcXVlc3Q6IEdlbmVyYXRvclJlcXVlc3RTY2hlbWEsXG4gICAgYnJhbmQ6IEJyYW5kUGFja2FnZVNjaGVtYSxcbiAgICBrcGlSZXNlYXJjaDogS3BpUmVzZWFyY2hSZXN1bHRTY2hlbWEsXG4gIH0pXG4gIC5zdHJpY3QoKTtcblxuY29uc3QgUGxhbk91dHB1dFNjaGVtYSA9IFJlc2VhcmNoT3V0cHV0U2NoZW1hLmV4dGVuZCh7XG4gIHBsYW46IEdlbmVyYXRvclBsYW5TY2hlbWEsXG59KS5zdHJpY3QoKTtcblxuY29uc3QgUHJvdmlzaW9uT3V0cHV0U2NoZW1hID0gUGxhbk91dHB1dFNjaGVtYS5leHRlbmQoe1xuICBsb2NhdGlvbjogR2VuZXJhdGVkU2NoZW1hU2NoZW1hLFxufSkuc3RyaWN0KCk7XG5cbmNvbnN0IE1hdGVyaWFsaXplT3V0cHV0U2NoZW1hID0gUHJvdmlzaW9uT3V0cHV0U2NoZW1hLmV4dGVuZCh7XG4gIG1hdGVyaWFsaXplZDogTWF0ZXJpYWxpemVkR2VuZXJhdG9yRGF0YVNjaGVtYSxcbn0pLnN0cmljdCgpO1xuXG4vKiogQnVpbGQgb25lIHR5cGVkIHdvcmtmbG93IG92ZXIgYW4gaXNvbGF0ZWQgZ2VuZXJhdG9yIG9wZXJhdGlvbiBvd25lci4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVHZW5lcmF0b3JXb3JrZmxvdyhcbiAgb3BlcmF0aW9uczogR2VuZXJhdG9yV29ya2Zsb3dPcGVyYXRpb25zLFxuICBvcHRpb25zOiBHZW5lcmF0b3JXb3JrZmxvd09wdGlvbnMgPSB7fSxcbikge1xuICBjb25zdCByZXNlYXJjaCA9IGNyZWF0ZVN0ZXAoe1xuICAgIGlkOiBcInJlc2VhcmNoLWJyYW5kLWFuZC1rcGlzXCIsXG4gICAgaW5wdXRTY2hlbWE6IEdlbmVyYXRvclJlcXVlc3RTY2hlbWEsXG4gICAgb3V0cHV0U2NoZW1hOiBSZXNlYXJjaE91dHB1dFNjaGVtYSxcbiAgICBleGVjdXRlOiBhc3luYyAoeyBpbnB1dERhdGEsIGFib3J0U2lnbmFsLCB3cml0ZXIgfSkgPT4ge1xuICAgICAgYXdhaXQgd3JpdGVyPy53cml0ZSh7IHR5cGU6IFwiZ2VuZXJhdG9yLXByb2dyZXNzXCIsIHN0YWdlOiBcInJlc2VhcmNoXCIsIHN0YXR1czogXCJzdGFydGVkXCIgfSk7XG4gICAgICBjb25zdCBicmFuZFJlcXVlc3Q6IEJyYW5kR2VuZXJhdGlvblJlcXVlc3QgPSB7XG4gICAgICAgIG5hbWU6IGlucHV0RGF0YS5jb21wYW55LFxuICAgICAgICAuLi4oaW5wdXREYXRhLndlYnNpdGUgPyB7IHdlYnNpdGU6IGlucHV0RGF0YS53ZWJzaXRlIH0gOiB7fSksXG4gICAgICB9O1xuICAgICAgY29uc3QgW2JyYW5kLCBrcGlSZXNlYXJjaF0gPSBhd2FpdCBQcm9taXNlLmFsbChbXG4gICAgICAgIG9wZXJhdGlvbnMuZ2VuZXJhdGVCcmFuZChicmFuZFJlcXVlc3QsIGFib3J0U2lnbmFsKSxcbiAgICAgICAgb3BlcmF0aW9ucy5yZXNlYXJjaEtwaXMoXG4gICAgICAgICAge1xuICAgICAgICAgICAgY29tcGFueTogaW5wdXREYXRhLmNvbXBhbnksXG4gICAgICAgICAgICBwdXJwb3NlOiBpbnB1dERhdGEucHVycG9zZSxcbiAgICAgICAgICAgIHF1ZXJpZXM6IGlucHV0RGF0YS5xdWVyaWVzLFxuICAgICAgICAgIH0sXG4gICAgICAgICAgYWJvcnRTaWduYWwsXG4gICAgICAgICksXG4gICAgICBdKTtcbiAgICAgIHJldHVybiB7IHJlcXVlc3Q6IGlucHV0RGF0YSwgYnJhbmQsIGtwaVJlc2VhcmNoIH07XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgcGxhbiA9IGNyZWF0ZVN0ZXAoe1xuICAgIGlkOiBcInBsYW4tZGVtby1kYXRhXCIsXG4gICAgaW5wdXRTY2hlbWE6IFJlc2VhcmNoT3V0cHV0U2NoZW1hLFxuICAgIG91dHB1dFNjaGVtYTogUGxhbk91dHB1dFNjaGVtYSxcbiAgICBleGVjdXRlOiBhc3luYyAoeyBpbnB1dERhdGEsIGFib3J0U2lnbmFsLCB3cml0ZXIgfSkgPT4ge1xuICAgICAgYXdhaXQgd3JpdGVyPy53cml0ZSh7IHR5cGU6IFwiZ2VuZXJhdG9yLXByb2dyZXNzXCIsIHN0YWdlOiBcInBsYW5cIiwgc3RhdHVzOiBcInN0YXJ0ZWRcIiB9KTtcbiAgICAgIGNvbnN0IGdlbmVyYXRlZFBsYW4gPSBhd2FpdCBvcGVyYXRpb25zLnBsYW5EYXRhKGlucHV0RGF0YSwgYWJvcnRTaWduYWwpO1xuICAgICAgcmV0dXJuIHsgLi4uaW5wdXREYXRhLCBwbGFuOiBnZW5lcmF0ZWRQbGFuIH07XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgYXBwcm92YWwgPSBjcmVhdGVTdGVwKHtcbiAgICBpZDogXCJhcHByb3ZlLW11dGF0aW9uLXBsYW5cIixcbiAgICBpbnB1dFNjaGVtYTogUGxhbk91dHB1dFNjaGVtYSxcbiAgICBvdXRwdXRTY2hlbWE6IFBsYW5PdXRwdXRTY2hlbWEsXG4gICAgcmVzdW1lU2NoZW1hOiB6Lm9iamVjdCh7XG4gICAgICBhcHByb3ZlZDogei5ib29sZWFuKCkuZGVzY3JpYmUoXCJXaGV0aGVyIHRoZSBnZW5lcmF0ZWQgbXV0YXRpb24gcGxhbiBpcyBhcHByb3ZlZC5cIiksXG4gICAgfSksXG4gICAgc3VzcGVuZFNjaGVtYTogei5vYmplY3Qoe1xuICAgICAgcmVhc29uOiB6LnN0cmluZygpLmRlc2NyaWJlKFwiV2h5IGFwcHJvdmFsIGlzIHJlcXVpcmVkLlwiKSxcbiAgICAgIHBsYW46IEdlbmVyYXRvclBsYW5TY2hlbWEsXG4gICAgfSksXG4gICAgZXhlY3V0ZTogYXN5bmMgKHsgaW5wdXREYXRhLCByZXN1bWVEYXRhLCBzdXNwZW5kIH0pID0+IHtcbiAgICAgIGlmIChvcHRpb25zLmFwcHJvdmFsID09PSBmYWxzZSkgcmV0dXJuIGlucHV0RGF0YTtcbiAgICAgIGlmIChyZXN1bWVEYXRhID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIHN1c3BlbmQoe1xuICAgICAgICAgIHJlYXNvbjogXCJBcHByb3ZlIHNjaGVtYSwgZGF0YSwgYW5kIHByZXNlbnRhdGlvbiBjcmVhdGlvbi5cIixcbiAgICAgICAgICBwbGFuOiBpbnB1dERhdGEucGxhbixcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgICBpZiAoIXJlc3VtZURhdGEuYXBwcm92ZWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiR2VuZXJhdG9yIG11dGF0aW9uIHBsYW4gd2FzIHJlamVjdGVkXCIpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGlucHV0RGF0YTtcbiAgICB9LFxuICB9KTtcblxuICBjb25zdCBwcm92aXNpb24gPSBjcmVhdGVTdGVwKHtcbiAgICBpZDogXCJwcm92aXNpb24tc2NoZW1hXCIsXG4gICAgaW5wdXRTY2hlbWE6IFBsYW5PdXRwdXRTY2hlbWEsXG4gICAgb3V0cHV0U2NoZW1hOiBQcm92aXNpb25PdXRwdXRTY2hlbWEsXG4gICAgZXhlY3V0ZTogYXN5bmMgKHsgaW5wdXREYXRhLCBhYm9ydFNpZ25hbCwgd3JpdGVyIH0pID0+IHtcbiAgICAgIGF3YWl0IHdyaXRlcj8ud3JpdGUoeyB0eXBlOiBcImdlbmVyYXRvci1wcm9ncmVzc1wiLCBzdGFnZTogXCJzY2hlbWFcIiwgc3RhdHVzOiBcInN0YXJ0ZWRcIiB9KTtcbiAgICAgIGNvbnN0IGxvY2F0aW9uID0gYXdhaXQgb3BlcmF0aW9ucy5wcm92aXNpb25TY2hlbWEoXG4gICAgICAgIHtcbiAgICAgICAgICByZXF1ZXN0OiB7XG4gICAgICAgICAgICBjb21wYW55OiBpbnB1dERhdGEucmVxdWVzdC5jb21wYW55LFxuICAgICAgICAgICAgcHVycG9zZTogaW5wdXREYXRhLnJlcXVlc3QucHVycG9zZSxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgICBhYm9ydFNpZ25hbCxcbiAgICAgICk7XG4gICAgICByZXR1cm4geyAuLi5pbnB1dERhdGEsIGxvY2F0aW9uIH07XG4gICAgfSxcbiAgfSk7XG5cbiAgY29uc3QgbWF0ZXJpYWxpemUgPSBjcmVhdGVTdGVwKHtcbiAgICBpZDogXCJtYXRlcmlhbGl6ZS1hbmQtdmFsaWRhdGUtZGF0YVwiLFxuICAgIGlucHV0U2NoZW1hOiBQcm92aXNpb25PdXRwdXRTY2hlbWEsXG4gICAgb3V0cHV0U2NoZW1hOiBNYXRlcmlhbGl6ZU91dHB1dFNjaGVtYSxcbiAgICBleGVjdXRlOiBhc3luYyAoeyBpbnB1dERhdGEsIGFib3J0U2lnbmFsLCB3cml0ZXIgfSkgPT4ge1xuICAgICAgYXdhaXQgd3JpdGVyPy53cml0ZSh7IHR5cGU6IFwiZ2VuZXJhdG9yLXByb2dyZXNzXCIsIHN0YWdlOiBcImRhdGFcIiwgc3RhdHVzOiBcInN0YXJ0ZWRcIiB9KTtcbiAgICAgIGNvbnN0IG1hdGVyaWFsaXplZCA9IGF3YWl0IG9wZXJhdGlvbnMubWF0ZXJpYWxpemVEYXRhKFxuICAgICAgICB7XG4gICAgICAgICAgbG9jYXRpb246IGlucHV0RGF0YS5sb2NhdGlvbixcbiAgICAgICAgICBwbGFuOiBpbnB1dERhdGEucGxhbixcbiAgICAgICAgfSxcbiAgICAgICAgYWJvcnRTaWduYWwsXG4gICAgICApO1xuICAgICAgcmV0dXJuIHsgLi4uaW5wdXREYXRhLCBtYXRlcmlhbGl6ZWQgfTtcbiAgICB9LFxuICB9KTtcblxuICBjb25zdCBwcmVzZW50YXRpb24gPSBjcmVhdGVTdGVwKHtcbiAgICBpZDogXCJjcmVhdGUtcHJlc2VudGF0aW9uXCIsXG4gICAgaW5wdXRTY2hlbWE6IE1hdGVyaWFsaXplT3V0cHV0U2NoZW1hLFxuICAgIG91dHB1dFNjaGVtYTogR2VuZXJhdGVkQXNzZXRzU2NoZW1hLFxuICAgIGV4ZWN1dGU6IGFzeW5jICh7IGlucHV0RGF0YSwgYWJvcnRTaWduYWwsIHdyaXRlciB9KSA9PiB7XG4gICAgICBhd2FpdCB3cml0ZXI/LndyaXRlKHtcbiAgICAgICAgdHlwZTogXCJnZW5lcmF0b3ItcHJvZ3Jlc3NcIixcbiAgICAgICAgc3RhZ2U6IFwicHJlc2VudGF0aW9uXCIsXG4gICAgICAgIHN0YXR1czogXCJzdGFydGVkXCIsXG4gICAgICB9KTtcbiAgICAgIGNvbnN0IHByZXNlbnRhdGlvbklucHV0ID0ge1xuICAgICAgICByZXF1ZXN0OiB7XG4gICAgICAgICAgY29tcGFueTogaW5wdXREYXRhLnJlcXVlc3QuY29tcGFueSxcbiAgICAgICAgICBwdXJwb3NlOiBpbnB1dERhdGEucmVxdWVzdC5wdXJwb3NlLFxuICAgICAgICB9LFxuICAgICAgICBsb2NhdGlvbjogaW5wdXREYXRhLmxvY2F0aW9uLFxuICAgICAgICBwbGFuOiBpbnB1dERhdGEucGxhbixcbiAgICAgICAgYnJhbmQ6IGlucHV0RGF0YS5icmFuZCxcbiAgICAgIH07XG4gICAgICBpZiAoaW5wdXREYXRhLnJlcXVlc3QucHJlc2VudGF0aW9uID09PSBcImRhc2hib2FyZFwiKSB7XG4gICAgICAgIGNvbnN0IGRhc2hib2FyZCA9IGF3YWl0IG9wZXJhdGlvbnMuY3JlYXRlRGFzaGJvYXJkKHByZXNlbnRhdGlvbklucHV0LCBhYm9ydFNpZ25hbCk7XG4gICAgICAgIHJldHVybiBHZW5lcmF0ZWRBc3NldHNTY2hlbWEucGFyc2Uoe1xuICAgICAgICAgIHJlcXVlc3Q6IGlucHV0RGF0YS5yZXF1ZXN0LFxuICAgICAgICAgIHNjaGVtYTogaW5wdXREYXRhLmxvY2F0aW9uLFxuICAgICAgICAgIHRhYmxlczogaW5wdXREYXRhLm1hdGVyaWFsaXplZC50YWJsZXMsXG4gICAgICAgICAgYnJhbmQ6IGlucHV0RGF0YS5icmFuZCxcbiAgICAgICAgICBrcGlSZXNlYXJjaDogaW5wdXREYXRhLmtwaVJlc2VhcmNoLFxuICAgICAgICAgIHBsYW46IGlucHV0RGF0YS5wbGFuLFxuICAgICAgICAgIGRhc2hib2FyZCxcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgICBjb25zdCBnZW5pZSA9IGF3YWl0IG9wZXJhdGlvbnMuY3JlYXRlR2VuaWUocHJlc2VudGF0aW9uSW5wdXQsIGFib3J0U2lnbmFsKTtcbiAgICAgIHJldHVybiBHZW5lcmF0ZWRBc3NldHNTY2hlbWEucGFyc2Uoe1xuICAgICAgICByZXF1ZXN0OiBpbnB1dERhdGEucmVxdWVzdCxcbiAgICAgICAgc2NoZW1hOiBpbnB1dERhdGEubG9jYXRpb24sXG4gICAgICAgIHRhYmxlczogaW5wdXREYXRhLm1hdGVyaWFsaXplZC50YWJsZXMsXG4gICAgICAgIGJyYW5kOiBpbnB1dERhdGEuYnJhbmQsXG4gICAgICAgIGtwaVJlc2VhcmNoOiBpbnB1dERhdGEua3BpUmVzZWFyY2gsXG4gICAgICAgIHBsYW46IGlucHV0RGF0YS5wbGFuLFxuICAgICAgICBnZW5pZSxcbiAgICAgIH0pO1xuICAgIH0sXG4gIH0pO1xuXG4gIHJldHVybiBjcmVhdGVXb3JrZmxvdyh7XG4gICAgaWQ6IFwiZ2VuZXJhdG9yLWRlbW8tYXNzZXRzXCIsXG4gICAgaW5wdXRTY2hlbWE6IEdlbmVyYXRvclJlcXVlc3RTY2hlbWEsXG4gICAgb3V0cHV0U2NoZW1hOiBHZW5lcmF0ZWRBc3NldHNTY2hlbWEsXG4gICAgb3B0aW9uczogeyBhdXRvUmVzdGFydEFjdGl2ZVJ1bnM6IGZhbHNlIH0sXG4gIH0pXG4gICAgLnRoZW4ocmVzZWFyY2gpXG4gICAgLnRoZW4ocGxhbilcbiAgICAudGhlbihhcHByb3ZhbClcbiAgICAudGhlbihwcm92aXNpb24pXG4gICAgLnRoZW4obWF0ZXJpYWxpemUpXG4gICAgLnRoZW4ocHJlc2VudGF0aW9uKVxuICAgIC5jb21taXQoKTtcbn1cblxuLyoqIEdlbmVyYXRvciB3b3JrZmxvdyB0eXBlLiAqL1xuZXhwb3J0IHR5cGUgR2VuZXJhdG9yV29ya2Zsb3cgPSBSZXR1cm5UeXBlPHR5cGVvZiBjcmVhdGVHZW5lcmF0b3JXb3JrZmxvdz47XG4iXX0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/assets.ts","../src/config.ts","../src/dashboard.ts","../src/data.ts","../src/defaults.ts","../src/generate.ts","../src/genie.ts","../src/kpi.ts","../src/namespace.ts","../src/operations.ts","../src/palette.ts","../src/plan.ts","../src/plugin.ts","../src/research.ts","../src/schema.ts","../src/structured.ts","../src/workflow.ts","../index.ts"],"version":"5.9.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,220 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbx-tools/appkit-generator",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
"description": "AppKit tools and Mastra workflows for researched Databricks demo assets",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/reggie-db/dbx-tools.git",
|
|
7
|
+
"directory": "packages/js/node/appkit-generator"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "projen build",
|
|
11
|
+
"compile": "projen compile",
|
|
12
|
+
"default": "projen default",
|
|
13
|
+
"package": "projen package",
|
|
14
|
+
"post-compile": "projen post-compile",
|
|
15
|
+
"pre-compile": "projen pre-compile",
|
|
16
|
+
"prepack": "projen prepack",
|
|
17
|
+
"test": "projen test",
|
|
18
|
+
"watch": "projen watch",
|
|
19
|
+
"projen": "projen"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^24.6.0",
|
|
23
|
+
"typescript": "^5.9.3"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@databricks/appkit": "0.81.0",
|
|
27
|
+
"@databricks/sdk-experimental": "^0.17.0",
|
|
28
|
+
"@dbx-tools/appkit": "^0.9.138",
|
|
29
|
+
"@dbx-tools/appkit-mastra": "^0.9.138",
|
|
30
|
+
"@dbx-tools/appkit-web-search": "^0.9.138",
|
|
31
|
+
"@dbx-tools/core": "^0.9.138",
|
|
32
|
+
"@dbx-tools/genie": "^0.9.138",
|
|
33
|
+
"@dbx-tools/shared-core": "^0.9.138",
|
|
34
|
+
"@dbx-tools/shared-generator": "^0.9.138",
|
|
35
|
+
"@dbx-tools/shared-genie": "^0.9.138",
|
|
36
|
+
"@mastra/core": "1.71.0",
|
|
37
|
+
"ai": "^7.0.0",
|
|
38
|
+
"cheerio": "^1.2.0",
|
|
39
|
+
"zod": "4.3.6"
|
|
40
|
+
},
|
|
41
|
+
"main": "./lib/index.js",
|
|
42
|
+
"license": "Apache-2.0",
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public",
|
|
45
|
+
"main": "./lib/index.js",
|
|
46
|
+
"types": "./lib/index.d.ts",
|
|
47
|
+
"exports": {
|
|
48
|
+
".": {
|
|
49
|
+
"types": "./lib/index.d.ts",
|
|
50
|
+
"default": "./lib/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./assets": {
|
|
53
|
+
"types": "./lib/src/assets.d.ts",
|
|
54
|
+
"default": "./lib/src/assets.js"
|
|
55
|
+
},
|
|
56
|
+
"./config": {
|
|
57
|
+
"types": "./lib/src/config.d.ts",
|
|
58
|
+
"default": "./lib/src/config.js"
|
|
59
|
+
},
|
|
60
|
+
"./dashboard": {
|
|
61
|
+
"types": "./lib/src/dashboard.d.ts",
|
|
62
|
+
"default": "./lib/src/dashboard.js"
|
|
63
|
+
},
|
|
64
|
+
"./data": {
|
|
65
|
+
"types": "./lib/src/data.d.ts",
|
|
66
|
+
"default": "./lib/src/data.js"
|
|
67
|
+
},
|
|
68
|
+
"./defaults": {
|
|
69
|
+
"types": "./lib/src/defaults.d.ts",
|
|
70
|
+
"default": "./lib/src/defaults.js"
|
|
71
|
+
},
|
|
72
|
+
"./generate": {
|
|
73
|
+
"types": "./lib/src/generate.d.ts",
|
|
74
|
+
"default": "./lib/src/generate.js"
|
|
75
|
+
},
|
|
76
|
+
"./genie": {
|
|
77
|
+
"types": "./lib/src/genie.d.ts",
|
|
78
|
+
"default": "./lib/src/genie.js"
|
|
79
|
+
},
|
|
80
|
+
"./kpi": {
|
|
81
|
+
"types": "./lib/src/kpi.d.ts",
|
|
82
|
+
"default": "./lib/src/kpi.js"
|
|
83
|
+
},
|
|
84
|
+
"./namespace": {
|
|
85
|
+
"types": "./lib/src/namespace.d.ts",
|
|
86
|
+
"default": "./lib/src/namespace.js"
|
|
87
|
+
},
|
|
88
|
+
"./operations": {
|
|
89
|
+
"types": "./lib/src/operations.d.ts",
|
|
90
|
+
"default": "./lib/src/operations.js"
|
|
91
|
+
},
|
|
92
|
+
"./palette": {
|
|
93
|
+
"types": "./lib/src/palette.d.ts",
|
|
94
|
+
"default": "./lib/src/palette.js"
|
|
95
|
+
},
|
|
96
|
+
"./plan": {
|
|
97
|
+
"types": "./lib/src/plan.d.ts",
|
|
98
|
+
"default": "./lib/src/plan.js"
|
|
99
|
+
},
|
|
100
|
+
"./plugin": {
|
|
101
|
+
"types": "./lib/src/plugin.d.ts",
|
|
102
|
+
"default": "./lib/src/plugin.js"
|
|
103
|
+
},
|
|
104
|
+
"./research": {
|
|
105
|
+
"types": "./lib/src/research.d.ts",
|
|
106
|
+
"default": "./lib/src/research.js"
|
|
107
|
+
},
|
|
108
|
+
"./schema": {
|
|
109
|
+
"types": "./lib/src/schema.d.ts",
|
|
110
|
+
"default": "./lib/src/schema.js"
|
|
111
|
+
},
|
|
112
|
+
"./structured": {
|
|
113
|
+
"types": "./lib/src/structured.d.ts",
|
|
114
|
+
"default": "./lib/src/structured.js"
|
|
115
|
+
},
|
|
116
|
+
"./workflow": {
|
|
117
|
+
"types": "./lib/src/workflow.d.ts",
|
|
118
|
+
"default": "./lib/src/workflow.js"
|
|
119
|
+
},
|
|
120
|
+
"./*": {
|
|
121
|
+
"types": "./lib/src/*/index.d.ts",
|
|
122
|
+
"default": "./lib/src/*/index.js"
|
|
123
|
+
},
|
|
124
|
+
"./package.json": "./package.json"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"version": "0.9.138",
|
|
128
|
+
"types": "./lib/index.d.ts",
|
|
129
|
+
"dbxToolsConfig": {
|
|
130
|
+
"tags": [
|
|
131
|
+
"node"
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
"type": "module",
|
|
135
|
+
"exports": {
|
|
136
|
+
".": {
|
|
137
|
+
"types": "./lib/index.d.ts",
|
|
138
|
+
"default": "./lib/index.js"
|
|
139
|
+
},
|
|
140
|
+
"./assets": {
|
|
141
|
+
"types": "./lib/src/assets.d.ts",
|
|
142
|
+
"default": "./lib/src/assets.js"
|
|
143
|
+
},
|
|
144
|
+
"./config": {
|
|
145
|
+
"types": "./lib/src/config.d.ts",
|
|
146
|
+
"default": "./lib/src/config.js"
|
|
147
|
+
},
|
|
148
|
+
"./dashboard": {
|
|
149
|
+
"types": "./lib/src/dashboard.d.ts",
|
|
150
|
+
"default": "./lib/src/dashboard.js"
|
|
151
|
+
},
|
|
152
|
+
"./data": {
|
|
153
|
+
"types": "./lib/src/data.d.ts",
|
|
154
|
+
"default": "./lib/src/data.js"
|
|
155
|
+
},
|
|
156
|
+
"./defaults": {
|
|
157
|
+
"types": "./lib/src/defaults.d.ts",
|
|
158
|
+
"default": "./lib/src/defaults.js"
|
|
159
|
+
},
|
|
160
|
+
"./generate": {
|
|
161
|
+
"types": "./lib/src/generate.d.ts",
|
|
162
|
+
"default": "./lib/src/generate.js"
|
|
163
|
+
},
|
|
164
|
+
"./genie": {
|
|
165
|
+
"types": "./lib/src/genie.d.ts",
|
|
166
|
+
"default": "./lib/src/genie.js"
|
|
167
|
+
},
|
|
168
|
+
"./kpi": {
|
|
169
|
+
"types": "./lib/src/kpi.d.ts",
|
|
170
|
+
"default": "./lib/src/kpi.js"
|
|
171
|
+
},
|
|
172
|
+
"./namespace": {
|
|
173
|
+
"types": "./lib/src/namespace.d.ts",
|
|
174
|
+
"default": "./lib/src/namespace.js"
|
|
175
|
+
},
|
|
176
|
+
"./operations": {
|
|
177
|
+
"types": "./lib/src/operations.d.ts",
|
|
178
|
+
"default": "./lib/src/operations.js"
|
|
179
|
+
},
|
|
180
|
+
"./palette": {
|
|
181
|
+
"types": "./lib/src/palette.d.ts",
|
|
182
|
+
"default": "./lib/src/palette.js"
|
|
183
|
+
},
|
|
184
|
+
"./plan": {
|
|
185
|
+
"types": "./lib/src/plan.d.ts",
|
|
186
|
+
"default": "./lib/src/plan.js"
|
|
187
|
+
},
|
|
188
|
+
"./plugin": {
|
|
189
|
+
"types": "./lib/src/plugin.d.ts",
|
|
190
|
+
"default": "./lib/src/plugin.js"
|
|
191
|
+
},
|
|
192
|
+
"./research": {
|
|
193
|
+
"types": "./lib/src/research.d.ts",
|
|
194
|
+
"default": "./lib/src/research.js"
|
|
195
|
+
},
|
|
196
|
+
"./schema": {
|
|
197
|
+
"types": "./lib/src/schema.d.ts",
|
|
198
|
+
"default": "./lib/src/schema.js"
|
|
199
|
+
},
|
|
200
|
+
"./structured": {
|
|
201
|
+
"types": "./lib/src/structured.d.ts",
|
|
202
|
+
"default": "./lib/src/structured.js"
|
|
203
|
+
},
|
|
204
|
+
"./workflow": {
|
|
205
|
+
"types": "./lib/src/workflow.d.ts",
|
|
206
|
+
"default": "./lib/src/workflow.js"
|
|
207
|
+
},
|
|
208
|
+
"./*": {
|
|
209
|
+
"types": "./lib/src/*/index.d.ts",
|
|
210
|
+
"default": "./lib/src/*/index.js"
|
|
211
|
+
},
|
|
212
|
+
"./package.json": "./package.json"
|
|
213
|
+
},
|
|
214
|
+
"files": [
|
|
215
|
+
"index.ts",
|
|
216
|
+
"src",
|
|
217
|
+
"lib"
|
|
218
|
+
],
|
|
219
|
+
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
|
|
220
|
+
}
|
package/src/assets.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic brand-asset discovery from an official page.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { object } from "@dbx-tools/shared-core";
|
|
8
|
+
import type { BrandAssetSet } from "@dbx-tools/shared-core/brand-utils";
|
|
9
|
+
import { load } from "cheerio";
|
|
10
|
+
|
|
11
|
+
/** Asset references extracted from one official HTML page. */
|
|
12
|
+
export interface DiscoveredBrandAssets {
|
|
13
|
+
/** Theme-aware icon reference. */
|
|
14
|
+
icon: BrandAssetSet;
|
|
15
|
+
/** Theme-aware logo reference. */
|
|
16
|
+
logo: BrandAssetSet;
|
|
17
|
+
/** Browser favicon reference. */
|
|
18
|
+
favicon: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Visual references extracted from an official mobile-store page. */
|
|
22
|
+
export interface DiscoveredMobileAppVisuals {
|
|
23
|
+
/** Store listing application name. */
|
|
24
|
+
appName: string;
|
|
25
|
+
/** App icon and screenshot URLs in page order. */
|
|
26
|
+
imageUrls: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Normalize a page-relative asset reference to a public HTTPS URL. */
|
|
30
|
+
function assetUrl(value: string | undefined, baseUrl: string): string | undefined {
|
|
31
|
+
if (!value?.trim()) return undefined;
|
|
32
|
+
try {
|
|
33
|
+
const resolved = new URL(value.trim(), baseUrl);
|
|
34
|
+
return resolved.protocol === "https:" ? resolved.toString() : undefined;
|
|
35
|
+
} catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Add a normalized URL to a candidate list once. */
|
|
41
|
+
function appendCandidate(candidates: string[], value: string | undefined, baseUrl: string): void {
|
|
42
|
+
const resolved = assetUrl(value, baseUrl);
|
|
43
|
+
if (resolved && !candidates.includes(resolved)) candidates.push(resolved);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Collapse CDN size and format variants of the same mobile artwork. */
|
|
47
|
+
function mobileImageIdentity(value: string): string {
|
|
48
|
+
const url = new URL(value);
|
|
49
|
+
if (/googleusercontent\.com$/i.test(url.hostname)) {
|
|
50
|
+
return `${url.origin}${url.pathname.replace(/=[^/]+$/, "")}`;
|
|
51
|
+
}
|
|
52
|
+
if (/mzstatic\.com$/i.test(url.hostname)) {
|
|
53
|
+
return `${url.origin}${url.pathname.replace(/\/[^/]+$/, "")}`;
|
|
54
|
+
}
|
|
55
|
+
return url.toString();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Find logo-like URLs nested in JSON-LD organization metadata.
|
|
60
|
+
*
|
|
61
|
+
* JSON-LD permits `logo` and `image` to be a URL, an object with a `url` or
|
|
62
|
+
* `contentUrl`, or an array. The bounded recursive walk accepts those forms
|
|
63
|
+
* without treating unrelated strings elsewhere in the document as assets.
|
|
64
|
+
*/
|
|
65
|
+
function jsonLdAssetUrls(value: unknown, depth = 0, assetContext = false): string[] {
|
|
66
|
+
if (depth > 8 || value === null || value === undefined) return [];
|
|
67
|
+
if (typeof value === "string") return assetContext ? [value] : [];
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
return value.flatMap((entry) => jsonLdAssetUrls(entry, depth + 1, assetContext));
|
|
70
|
+
}
|
|
71
|
+
if (!object.isRecord(value)) return [];
|
|
72
|
+
|
|
73
|
+
const direct = assetContext
|
|
74
|
+
? [value.url, value.contentUrl].flatMap((entry) => (typeof entry === "string" ? [entry] : []))
|
|
75
|
+
: [];
|
|
76
|
+
return [
|
|
77
|
+
...direct,
|
|
78
|
+
...jsonLdAssetUrls(value.logo, depth + 1, true),
|
|
79
|
+
...jsonLdAssetUrls(value.image, depth + 1, true),
|
|
80
|
+
...jsonLdAssetUrls(value["@graph"], depth + 1, false),
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Collect mobile screenshots and image objects from JSON-LD. */
|
|
85
|
+
function jsonLdMobileImageUrls(value: unknown, depth = 0, imageContext = false): string[] {
|
|
86
|
+
if (depth > 10 || value === null || value === undefined) return [];
|
|
87
|
+
if (typeof value === "string") return imageContext ? [value] : [];
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
return value.flatMap((entry) => jsonLdMobileImageUrls(entry, depth + 1, imageContext));
|
|
90
|
+
}
|
|
91
|
+
if (!object.isRecord(value)) return [];
|
|
92
|
+
const direct = imageContext
|
|
93
|
+
? [value.url, value.contentUrl].flatMap((entry) => (typeof entry === "string" ? [entry] : []))
|
|
94
|
+
: [];
|
|
95
|
+
return [
|
|
96
|
+
...direct,
|
|
97
|
+
...jsonLdMobileImageUrls(value.screenshot, depth + 1, true),
|
|
98
|
+
...jsonLdMobileImageUrls(value.image, depth + 1, true),
|
|
99
|
+
...jsonLdMobileImageUrls(value.thumbnailUrl, depth + 1, true),
|
|
100
|
+
...jsonLdMobileImageUrls(value["@graph"], depth + 1, false),
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Discover icon, logo, and favicon URLs from an official page.
|
|
106
|
+
*
|
|
107
|
+
* The function only returns URLs present in page metadata or markup. When the
|
|
108
|
+
* page omits an icon, it uses the conventional `/favicon.ico` URL on the same
|
|
109
|
+
* origin. The logo falls back to that icon so a generated context never points
|
|
110
|
+
* at the dbx-tools default artwork.
|
|
111
|
+
*/
|
|
112
|
+
export function discoverBrandAssets(html: string, pageUrl: string): DiscoveredBrandAssets {
|
|
113
|
+
const $ = load(html);
|
|
114
|
+
const icons: string[] = [];
|
|
115
|
+
const logos: string[] = [];
|
|
116
|
+
|
|
117
|
+
$("link[rel]").each((_index, element) => {
|
|
118
|
+
const rel = ($(element).attr("rel") ?? "").toLowerCase();
|
|
119
|
+
if (rel.split(/\s+/).some((part) => part === "icon" || part === "apple-touch-icon")) {
|
|
120
|
+
appendCandidate(icons, $(element).attr("href"), pageUrl);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
for (const selector of [
|
|
125
|
+
'meta[property="og:image"]',
|
|
126
|
+
'meta[property="og:logo"]',
|
|
127
|
+
'meta[name="twitter:image"]',
|
|
128
|
+
]) {
|
|
129
|
+
$(selector).each((_index, element) => {
|
|
130
|
+
appendCandidate(logos, $(element).attr("content"), pageUrl);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
$("img").each((_index, element) => {
|
|
135
|
+
const descriptor = [
|
|
136
|
+
$(element).attr("alt"),
|
|
137
|
+
$(element).attr("class"),
|
|
138
|
+
$(element).attr("id"),
|
|
139
|
+
$(element).attr("data-testid"),
|
|
140
|
+
]
|
|
141
|
+
.filter(Boolean)
|
|
142
|
+
.join(" ");
|
|
143
|
+
if (/\b(?:brand|logo|wordmark)\b/i.test(descriptor)) {
|
|
144
|
+
appendCandidate(logos, $(element).attr("src"), pageUrl);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
$('script[type="application/ld+json"]').each((_index, element) => {
|
|
149
|
+
try {
|
|
150
|
+
const value: unknown = JSON.parse($(element).text());
|
|
151
|
+
for (const candidate of jsonLdAssetUrls(value)) {
|
|
152
|
+
appendCandidate(logos, candidate, pageUrl);
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const fallbackIcon = new URL("/favicon.ico", pageUrl).toString();
|
|
160
|
+
const icon = icons[0] ?? logos[0] ?? fallbackIcon;
|
|
161
|
+
const logo = logos[0] ?? icons[0] ?? fallbackIcon;
|
|
162
|
+
return {
|
|
163
|
+
icon: { light: icon },
|
|
164
|
+
logo: { light: logo },
|
|
165
|
+
favicon: icon,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Extract an app name plus representative screenshots/icons from a store page. */
|
|
170
|
+
export function discoverMobileAppVisuals(
|
|
171
|
+
html: string,
|
|
172
|
+
pageUrl: string,
|
|
173
|
+
): DiscoveredMobileAppVisuals {
|
|
174
|
+
const $ = load(html);
|
|
175
|
+
const metadataImages: string[] = [];
|
|
176
|
+
const structuredImages: string[] = [];
|
|
177
|
+
const screenshotImages: string[] = [];
|
|
178
|
+
const iconImages: string[] = [];
|
|
179
|
+
const otherStoreImages: string[] = [];
|
|
180
|
+
for (const selector of ['meta[property="og:image"]', 'meta[name="twitter:image"]']) {
|
|
181
|
+
$(selector).each((_index, element) => {
|
|
182
|
+
appendCandidate(metadataImages, $(element).attr("content"), pageUrl);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
$('script[type="application/ld+json"]').each((_index, element) => {
|
|
186
|
+
try {
|
|
187
|
+
const value: unknown = JSON.parse($(element).text());
|
|
188
|
+
for (const candidate of jsonLdMobileImageUrls(value)) {
|
|
189
|
+
appendCandidate(structuredImages, candidate, pageUrl);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
$("img").each((_index, element) => {
|
|
196
|
+
const descriptor = [
|
|
197
|
+
$(element).attr("alt"),
|
|
198
|
+
$(element).attr("class"),
|
|
199
|
+
$(element).attr("aria-label"),
|
|
200
|
+
]
|
|
201
|
+
.filter(Boolean)
|
|
202
|
+
.join(" ");
|
|
203
|
+
const source = $(element).attr("src") ?? $(element).attr("data-src");
|
|
204
|
+
const isStoreImageHost = /(?:mzstatic|ggpht|googleusercontent)\./i.test(source ?? "");
|
|
205
|
+
const target = /\b(?:phone|screen|screenshot|tablet)\b/i.test(descriptor)
|
|
206
|
+
? screenshotImages
|
|
207
|
+
: /\b(?:app|icon)\b/i.test(descriptor)
|
|
208
|
+
? iconImages
|
|
209
|
+
: isStoreImageHost
|
|
210
|
+
? otherStoreImages
|
|
211
|
+
: undefined;
|
|
212
|
+
if (target) {
|
|
213
|
+
appendCandidate(target, source, pageUrl);
|
|
214
|
+
const srcset = ($(element).attr("srcset") ?? $(element).attr("data-srcset"))
|
|
215
|
+
?.split(",")
|
|
216
|
+
.at(-1)
|
|
217
|
+
?.trim()
|
|
218
|
+
.split(/\s+/)[0];
|
|
219
|
+
appendCandidate(target, srcset, pageUrl);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
$("picture source[srcset]").each((_index, element) => {
|
|
223
|
+
const srcset = $(element).attr("srcset")?.split(",").at(-1)?.trim().split(/\s+/)[0];
|
|
224
|
+
appendCandidate(screenshotImages, srcset, pageUrl);
|
|
225
|
+
});
|
|
226
|
+
const images: string[] = [];
|
|
227
|
+
const seenImages = new Set<string>();
|
|
228
|
+
for (const candidate of [
|
|
229
|
+
...metadataImages.slice(0, 1),
|
|
230
|
+
...screenshotImages,
|
|
231
|
+
...iconImages,
|
|
232
|
+
...structuredImages,
|
|
233
|
+
...otherStoreImages,
|
|
234
|
+
]) {
|
|
235
|
+
const resolved = assetUrl(candidate, pageUrl);
|
|
236
|
+
if (!resolved) continue;
|
|
237
|
+
const identity = mobileImageIdentity(resolved);
|
|
238
|
+
if (seenImages.has(identity)) continue;
|
|
239
|
+
seenImages.add(identity);
|
|
240
|
+
images.push(resolved);
|
|
241
|
+
}
|
|
242
|
+
const rawName =
|
|
243
|
+
$('meta[property="og:title"]').attr("content") ?? $("h1").first().text() ?? $("title").text();
|
|
244
|
+
const appName =
|
|
245
|
+
rawName.replace(/\s+(?:on the App Store|- Apps on Google Play).*$/i, "").trim() ||
|
|
246
|
+
new URL(pageUrl).hostname;
|
|
247
|
+
return { appName, imageUrls: images.slice(0, 6) };
|
|
248
|
+
}
|