@customyai/cli 0.1.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.
@@ -0,0 +1,378 @@
1
+ import { z } from 'zod';
2
+ import { mkdir, writeFile, readFile } from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ // src/codegen.ts
6
+ var literal = (value) => JSON.stringify(value);
7
+ var union = (values) => values.length ? values.map(literal).join(" | ") : "never";
8
+ var key = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : literal(name);
9
+ function schemaToType(schema, indent = "") {
10
+ if (!schema || typeof schema !== "object") return "unknown";
11
+ if (Array.isArray(schema.enum)) return schema.enum.length ? schema.enum.map(literal).join(" | ") : "never";
12
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
13
+ if (types.length > 1) return types.map((type) => schemaToType({ ...schema, type }, indent)).join(" | ");
14
+ switch (types[0]) {
15
+ case "string":
16
+ return "string";
17
+ case "number":
18
+ case "integer":
19
+ return "number";
20
+ case "boolean":
21
+ return "boolean";
22
+ case "null":
23
+ return "null";
24
+ case "array":
25
+ return `Array<${schemaToType(schema.items, indent)}>`;
26
+ case "object": {
27
+ const required = new Set(schema.required ?? []);
28
+ const inner = `${indent} `;
29
+ const fields = Object.entries(schema.properties ?? {}).map(([name, value]) => `${inner}${key(name)}${required.has(name) ? "" : "?"}: ${schemaToType(value, inner)};`);
30
+ if (schema.additionalProperties !== false && !schema.properties) return "Record<string, unknown>";
31
+ return fields.length ? `{
32
+ ${fields.join("\n")}
33
+ ${indent}}` : "Record<string, never>";
34
+ }
35
+ default:
36
+ return "unknown";
37
+ }
38
+ }
39
+ function generateAppTypes(manifest) {
40
+ const events = manifest.events.map((event) => ` ${literal(event.name)}: ${schemaToType(event.properties, " ")};`);
41
+ return [
42
+ `// Generado por \`customy apps codegen\` desde customy.app.json (${manifest.key}). No editar a mano.`,
43
+ "",
44
+ `export type CustomyEventName = ${union(manifest.events.map((event) => event.name))};`,
45
+ "",
46
+ "export interface CustomyEventProperties {",
47
+ ...events,
48
+ "}",
49
+ "",
50
+ `export type CustomyCapability = ${union(manifest.capabilities.map((capability) => capability.lookupKey))};`,
51
+ `export type CustomyPlan = ${union(manifest.plans.map((plan) => plan.code))};`,
52
+ `export type CustomyMeter = ${union(manifest.meters.map((meter) => meter.code))};`,
53
+ "",
54
+ "/** Prop\xF3sitos de consentimiento que exige cada evento. */",
55
+ `export const CUSTOMY_EVENT_PURPOSES = {
56
+ ${manifest.events.map((event) => ` ${literal(event.name)}: ${literal(event.purposes)},`).join("\n")}
57
+ } as const;`,
58
+ ""
59
+ ].join("\n");
60
+ }
61
+ var EntitlementMeterAggregationSchema = z.enum(["sum", "count", "last"]);
62
+ var identifier = z.string().regex(/^[a-z][a-z0-9-]{1,62}$/);
63
+ var ConnectedOperationSchema = z.enum([
64
+ "read",
65
+ "propose",
66
+ "submit",
67
+ "read_owned",
68
+ "write_owned",
69
+ "research",
70
+ "deliver_budget_alert",
71
+ "deliver_notification"
72
+ ]);
73
+ var ConnectedBindingKindSchema = z.enum([
74
+ "product",
75
+ "internal_service",
76
+ "external_destination",
77
+ "public_form",
78
+ "private_collection",
79
+ "external_source",
80
+ "external_automation",
81
+ "external_communication"
82
+ ]);
83
+ var ProductBindingSchema = z.object({
84
+ kind: ConnectedBindingKindSchema,
85
+ /** Claves de recurso admitidas; sin lista, cualquiera. Son nombres del producto, nunca de apps. */
86
+ keys: z.array(identifier).min(1).optional(),
87
+ /** Patrón que debe cumplir el identificador del recurso en el producto. */
88
+ resourceIdPattern: z.string().min(1).optional(),
89
+ operations: z.object({
90
+ allowed: z.array(ConnectedOperationSchema).max(4),
91
+ /** exact: exactamente `allowed`; subset: un subconjunto no vacío. */
92
+ mode: z.enum(["exact", "subset"]),
93
+ /** false: las operaciones pueden omitirse (si se dan, cumplen el modo). */
94
+ required: z.boolean().default(true)
95
+ }).strict().optional(),
96
+ maxPerConnection: z.number().int().positive().optional()
97
+ }).strict();
98
+ var ProductManifestSchema = z.object({
99
+ schema: z.literal("product/v1"),
100
+ key: z.string().regex(/^customy-[a-z][a-z0-9-]*$/),
101
+ /** Audiencia de sus tokens de máquina (discovery). */
102
+ audience: z.string().regex(/^customy-[a-z][a-z0-9-]*$/),
103
+ /** Si true, todo enlace debe declarar `bindingKind` (no admite el enlace implícito). */
104
+ bindingRequired: z.boolean().default(false),
105
+ bindings: z.array(ProductBindingSchema).default([]),
106
+ /** Productos a los que llama en nombre de una app (token exchange). Solo esos lo aceptan como actor. */
107
+ requires: z.array(z.string().regex(/^customy-[a-z][a-z0-9-]*$/)).default([])
108
+ }).strict().superRefine((manifest, context) => {
109
+ const kinds = manifest.bindings.map((binding) => binding.kind);
110
+ if (new Set(kinds).size !== kinds.length) context.addIssue({ code: z.ZodIssueCode.custom, message: "Binding kinds must be unique per product" });
111
+ if (manifest.requires.includes(manifest.key)) context.addIssue({ code: z.ZodIssueCode.custom, message: "A product cannot require itself" });
112
+ });
113
+ var product = (key2, bindings = [], bindingRequired = false, requires = []) => ProductManifestSchema.parse({ schema: "product/v1", key: key2, audience: key2, bindingRequired, bindings, requires });
114
+ var CONNECTABLE_PRODUCT_MANIFESTS = [
115
+ product("customy-atlas", [{ kind: "internal_service", operations: { allowed: ["read", "propose"], mode: "subset" } }]),
116
+ product("customy-tables", [{ kind: "private_collection", keys: ["favorites", "comparisons", "preferences"], operations: { allowed: ["read_owned", "write_owned"], mode: "subset" } }]),
117
+ product("customy-agent", [{ kind: "external_automation", keys: ["catalog-updater"], operations: { allowed: ["research", "deliver_budget_alert"], mode: "subset" } }]),
118
+ product("customy-forms", [{ kind: "public_form", keys: ["subscription", "contact", "commercial"], resourceIdPattern: "^frm_[a-zA-Z0-9_-]+$", operations: { allowed: ["read", "submit"], mode: "exact" } }], false, ["customy-links"]),
119
+ product("customy-crm"),
120
+ product("customy-content", [{ kind: "external_destination", operations: { allowed: ["read"], mode: "exact" } }]),
121
+ product("customy-storage"),
122
+ product("customy-data", [{ kind: "external_source", keys: ["analytics-source"], operations: { allowed: ["submit"], mode: "exact" } }]),
123
+ product("customy-analytics"),
124
+ product("customy-campaigns", [{ kind: "external_communication", keys: ["weekly-brief"], operations: { allowed: ["read"], mode: "exact" } }]),
125
+ product("customy-engagement", [{ kind: "external_communication", keys: ["notification-delivery"], operations: { allowed: ["deliver_notification"], mode: "exact" } }]),
126
+ // Una referencia de solo lectura a un sitio de Pages del Workspace; no concede edición ni publicación.
127
+ product("customy-pages", [{ kind: "product", resourceIdPattern: "^[0-9]{6,20}$", operations: { allowed: ["read"], mode: "exact", required: false }, maxPerConnection: 1 }], true)
128
+ ];
129
+ function connectedResourceViolation(resource, manifests = CONNECTABLE_PRODUCT_MANIFESTS) {
130
+ const manifest = manifests.find((entry) => entry.key === resource.productKey);
131
+ if (!manifest) return "PRODUCT_NOT_CONNECTABLE";
132
+ if (manifest.bindingRequired && !resource.bindingKind) return "BINDING_KIND_REQUIRED";
133
+ const binding = resource.bindingKind ? manifest.bindings.find((entry) => entry.kind === resource.bindingKind) : void 0;
134
+ if (!binding) {
135
+ const plainReference = resource.bindingKind === void 0 || resource.bindingKind === "product";
136
+ if (plainReference && !resource.operations) return null;
137
+ return plainReference ? "OPERATIONS_REQUIRE_BINDING" : "BINDING_NOT_OFFERED";
138
+ }
139
+ if (binding.keys && !binding.keys.includes(resource.key)) return "BINDING_KEY_NOT_ALLOWED";
140
+ if (binding.resourceIdPattern && !new RegExp(binding.resourceIdPattern).test(resource.resourceId)) return "BINDING_RESOURCE_ID_INVALID";
141
+ const rule = binding.operations;
142
+ const operations = resource.operations;
143
+ if (!rule) return operations ? "OPERATIONS_REQUIRE_BINDING" : null;
144
+ if (!operations || operations.length === 0) return rule.required ? "BINDING_OPERATIONS_REQUIRED" : null;
145
+ if (operations.some((operation) => !rule.allowed.includes(operation))) return "BINDING_OPERATION_NOT_ALLOWED";
146
+ if (rule.mode === "exact" && (operations.length !== rule.allowed.length || rule.allowed.some((operation) => !operations.includes(operation)))) return "BINDING_OPERATIONS_MISMATCH";
147
+ return null;
148
+ }
149
+ function connectedResourceLimit(productKey2, manifests = CONNECTABLE_PRODUCT_MANIFESTS) {
150
+ const limits = manifests.find((entry) => entry.key === productKey2)?.bindings.map((binding) => binding.maxPerConnection).filter((value) => value !== void 0) ?? [];
151
+ return limits.length ? Math.min(...limits) : null;
152
+ }
153
+
154
+ // src/vendor/app-manifest.ts
155
+ var slug = z.string().regex(/^[a-z][a-z0-9-]{1,38}[a-z0-9]$/);
156
+ var productKey = z.string().regex(/^customy-[a-z][a-z0-9-]*$/);
157
+ var httpsOrigin = z.string().url().max(512).refine((value) => {
158
+ const url = new URL(value);
159
+ return url.origin === value && (url.protocol === "https:" || ["localhost", "127.0.0.1"].includes(url.hostname));
160
+ }, "Origins must be bare https origins");
161
+ var eventName = z.string().regex(/^[a-z][a-z0-9_.:-]{0,199}$/);
162
+ var lookupKey = z.string().regex(/^[a-z][a-z0-9_.:-]{0,79}$/);
163
+ var AppProductUseSchema = z.object({
164
+ product: productKey,
165
+ /** Scopes de producto que pedirán sus credenciales de máquina (`<producto>:<scope>`). */
166
+ scopes: z.array(z.string().regex(/^[a-z][a-z0-9_-]*:[a-z0-9_:.-]+$/)).max(64).default([])
167
+ }).strict();
168
+ var AppResourceSchema = z.object({
169
+ key: z.string().regex(/^[a-z][a-z0-9-]{1,62}$/),
170
+ product: productKey,
171
+ bindingKind: ConnectedBindingKindSchema.optional(),
172
+ operations: z.array(ConnectedOperationSchema).min(1).max(2).optional()
173
+ }).strict();
174
+ var AppEventSchema = z.object({
175
+ name: eventName,
176
+ type: z.enum(["track", "identify", "group", "page", "screen", "alias"]),
177
+ schemaVersion: z.number().int().positive(),
178
+ /** JSON Schema de `properties`; se publica como schema fijado del evento. */
179
+ properties: z.record(z.unknown()).default({ type: "object" }),
180
+ purposes: z.array(z.string().regex(/^[a-z][a-z0-9_.:-]{0,79}$/)).min(1).max(8)
181
+ }).strict();
182
+ var AppCapabilitySchema = z.object({
183
+ lookupKey,
184
+ name: z.string().min(1).max(120),
185
+ type: z.enum(["boolean", "metered", "config"]),
186
+ meter: lookupKey.optional()
187
+ }).strict();
188
+ var AppPlanSchema = z.object({
189
+ code: lookupKey,
190
+ name: z.string().min(1).max(120),
191
+ rank: z.number().int().nonnegative().default(100),
192
+ capabilities: z.record(lookupKey, z.unknown()).default({})
193
+ }).strict();
194
+ var AppMeterSchema = z.object({
195
+ code: lookupKey,
196
+ aggregation: EntitlementMeterAggregationSchema,
197
+ unit: z.string().min(1).max(40)
198
+ }).strict();
199
+ var AppManifestSchema = z.object({
200
+ /** Referencia opcional al JSON Schema publicado, para autocompletado en editores. */
201
+ $schema: z.string().url().max(200).optional(),
202
+ schema: z.literal("app/v1"),
203
+ key: slug,
204
+ name: z.string().trim().min(1).max(80),
205
+ origins: z.object({ staging: httpsOrigin.optional(), production: httpsOrigin.optional() }).strict().refine((origins) => Boolean(origins.staging || origins.production), "Declare at least one origin"),
206
+ products: z.array(AppProductUseSchema).max(32).default([]),
207
+ resources: z.array(AppResourceSchema).max(64).default([]),
208
+ events: z.array(AppEventSchema).max(256).default([]),
209
+ capabilities: z.array(AppCapabilitySchema).max(256).default([]),
210
+ plans: z.array(AppPlanSchema).max(32).default([]),
211
+ meters: z.array(AppMeterSchema).max(64).default([])
212
+ }).strict();
213
+ var APP_ACCESS_SCOPES = ["capabilities:read", "users:contact:read"];
214
+ var APP_ACCESS_SCOPE_SET = new Set(APP_ACCESS_SCOPES);
215
+ function appScopeAllowed(product2, scope) {
216
+ if (product2 === "customy-access") return APP_ACCESS_SCOPE_SET.has(scope);
217
+ return scope.startsWith(`${product2.replace(/^customy-/, "")}:`);
218
+ }
219
+ function appManifestProblems(input, manifests = CONNECTABLE_PRODUCT_MANIFESTS) {
220
+ const parsed = AppManifestSchema.safeParse(input);
221
+ if (!parsed.success) return parsed.error.issues.map((issue) => ({ path: issue.path.join("."), code: "SCHEMA_INVALID" }));
222
+ const app = parsed.data;
223
+ const problems = [];
224
+ const unique = (items, key2, path2) => {
225
+ const seen = /* @__PURE__ */ new Set();
226
+ items.forEach((item, index) => {
227
+ const k = key2(item);
228
+ if (seen.has(k)) problems.push({ path: `${path2}.${index}`, code: "DUPLICATE" });
229
+ seen.add(k);
230
+ });
231
+ };
232
+ unique(app.products, (p) => p.product, "products");
233
+ unique(app.resources, (r) => r.key, "resources");
234
+ unique(app.events, (e) => e.name, "events");
235
+ unique(app.capabilities, (c) => c.lookupKey, "capabilities");
236
+ unique(app.plans, (p) => p.code, "plans");
237
+ unique(app.meters, (m) => m.code, "meters");
238
+ app.products.forEach((use, index) => {
239
+ use.scopes.forEach((scope, scopeIndex) => {
240
+ if (!appScopeAllowed(use.product, scope)) problems.push({ path: `products.${index}.scopes.${scopeIndex}`, code: "SCOPE_OUTSIDE_PRODUCT" });
241
+ });
242
+ });
243
+ const counts = /* @__PURE__ */ new Map();
244
+ app.resources.forEach((resource, index) => {
245
+ const violation = connectedResourceViolation({ ...resource, productKey: resource.product, resourceId: "manifest" }, manifests);
246
+ if (violation && violation !== "BINDING_RESOURCE_ID_INVALID") problems.push({ path: `resources.${index}`, code: violation });
247
+ counts.set(resource.product, (counts.get(resource.product) ?? 0) + 1);
248
+ });
249
+ for (const [product2, count] of counts) {
250
+ const limit = connectedResourceLimit(product2, manifests);
251
+ if (limit !== null && count > limit) problems.push({ path: "resources", code: "BINDING_LIMIT_EXCEEDED" });
252
+ }
253
+ const meters = new Set(app.meters.map((meter) => meter.code));
254
+ const capabilities = new Set(app.capabilities.map((capability) => capability.lookupKey));
255
+ app.capabilities.forEach((capability, index) => {
256
+ if (capability.type === "metered" && (!capability.meter || !meters.has(capability.meter))) problems.push({ path: `capabilities.${index}.meter`, code: "METER_UNDECLARED" });
257
+ if (capability.type !== "metered" && capability.meter) problems.push({ path: `capabilities.${index}.meter`, code: "METER_ON_UNMETERED" });
258
+ });
259
+ app.plans.forEach((plan, index) => {
260
+ for (const key2 of Object.keys(plan.capabilities)) if (!capabilities.has(key2)) problems.push({ path: `plans.${index}.capabilities.${key2}`, code: "CAPABILITY_UNDECLARED" });
261
+ });
262
+ return problems;
263
+ }
264
+
265
+ // src/apps.ts
266
+ function validateManifest(input) {
267
+ const problems = appManifestProblems(input);
268
+ return problems.length ? { ok: false, problems } : { ok: true, manifest: AppManifestSchema.parse(input) };
269
+ }
270
+ var SyncError = class extends Error {
271
+ constructor(code, status) {
272
+ super(`${code} (${status})`);
273
+ this.code = code;
274
+ this.status = status;
275
+ }
276
+ code;
277
+ status;
278
+ };
279
+ async function syncApp(options) {
280
+ const base = options.accessUrl.replace(/\/$/, "");
281
+ if (!/^https:\/\//.test(base) && !/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(base)) throw new SyncError("ACCESS_URL_INSECURE", 0);
282
+ const call = async (method, path2, body) => {
283
+ const response = await (options.fetch ?? fetch)(`${base}${path2}`, {
284
+ method,
285
+ headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json", "idempotency-key": crypto.randomUUID() },
286
+ body: JSON.stringify(body)
287
+ });
288
+ const json = await response.json().catch(() => ({}));
289
+ if (!response.ok) throw new SyncError(String(json.code ?? json.error ?? "ACCESS_REQUEST_FAILED"), response.status);
290
+ return json;
291
+ };
292
+ const env = encodeURIComponent(options.workspaceEnvironmentId);
293
+ const installed = await call("POST", `/api/admin/env/${env}/connected-applications/install`, { manifest: options.manifest, reason: options.reason });
294
+ const connection = installed.connection;
295
+ const reconciliation = (value) => value && typeof value === "object" ? value : {};
296
+ if (!installed.replayed) {
297
+ return { action: "installed", applicationId: connection.applicationId, environmentId: connection.environmentId, revision: connection.revision, reconciliation: reconciliation(installed.reconciliation) };
298
+ }
299
+ const updated = await call("PUT", `/api/admin/env/${encodeURIComponent(connection.environmentId)}/connected-application/manifest`, {
300
+ expectedRevision: connection.revision,
301
+ manifest: options.manifest,
302
+ reason: options.reason
303
+ });
304
+ const revision = Number(updated.revision ?? connection.revision);
305
+ return { action: updated.unchanged ? "unchanged" : "updated", applicationId: connection.applicationId, environmentId: connection.environmentId, revision, reconciliation: reconciliation(updated.reconciliation) };
306
+ }
307
+ function option(args, name) {
308
+ const index = args.indexOf(`--${name}`);
309
+ return index >= 0 ? args[index + 1] : void 0;
310
+ }
311
+ async function loadManifest(args, io) {
312
+ const file = path.resolve(io.cwd, option(args, "file") ?? "customy.app.json");
313
+ let raw;
314
+ try {
315
+ raw = JSON.parse(await readFile(file, "utf8"));
316
+ } catch (error) {
317
+ io.err(`no se pudo leer ${path.relative(io.cwd, file) || file}: ${error instanceof Error ? error.message : error}`);
318
+ return null;
319
+ }
320
+ const result = validateManifest(raw);
321
+ if (!result.ok) {
322
+ for (const problem of result.problems) io.err(`\u2717 ${problem.path || "(ra\xEDz)"}: ${problem.code}`);
323
+ return null;
324
+ }
325
+ return result.manifest;
326
+ }
327
+ async function runCli(argv, io) {
328
+ const [group, command, ...args] = argv;
329
+ if (group !== "apps" || !["validate", "codegen", "sync"].includes(command ?? "")) {
330
+ io.err("uso: customy apps <validate|codegen|sync> [--file customy.app.json]");
331
+ return 2;
332
+ }
333
+ const manifest = await loadManifest(args, io);
334
+ if (!manifest) return 1;
335
+ if (command === "validate") {
336
+ io.out(`\u2713 ${manifest.key}: manifiesto app/v1 v\xE1lido (${manifest.events.length} eventos, ${manifest.capabilities.length} capabilities)`);
337
+ return 0;
338
+ }
339
+ if (command === "codegen") {
340
+ const out = path.resolve(io.cwd, option(args, "out") ?? "customy.generated.ts");
341
+ await mkdir(path.dirname(out), { recursive: true });
342
+ await writeFile(out, generateAppTypes(manifest));
343
+ io.out(`\u2713 tipos de ${manifest.key} en ${path.relative(io.cwd, out)}`);
344
+ return 0;
345
+ }
346
+ const workspaceEnvironmentId = option(args, "workspace-env");
347
+ const accessUrl = io.env.CUSTOMY_ACCESS_URL;
348
+ const token = io.env.CUSTOMY_ACCESS_TOKEN;
349
+ if (!workspaceEnvironmentId || !accessUrl || !token) {
350
+ io.err("sync necesita --workspace-env y las variables CUSTOMY_ACCESS_URL y CUSTOMY_ACCESS_TOKEN");
351
+ return 2;
352
+ }
353
+ try {
354
+ const result = await syncApp({
355
+ manifest,
356
+ accessUrl,
357
+ token,
358
+ workspaceEnvironmentId,
359
+ reason: option(args, "reason") ?? `customy apps sync ${manifest.key}`,
360
+ fetch: io.fetch
361
+ });
362
+ const verb = { installed: "instalada", updated: "actualizada", unchanged: "sin cambios" }[result.action];
363
+ io.out(`\u2713 ${manifest.key} ${verb}: aplicaci\xF3n ${result.applicationId}, entorno ${result.environmentId}, revisi\xF3n ${result.revision}`);
364
+ let failed = false;
365
+ for (const [product2, outcome] of Object.entries(result.reconciliation)) {
366
+ if (outcome.status === "failed") {
367
+ failed = true;
368
+ io.err(`\u2717 ${product2}: ${outcome.code} \u2014 vuelve a ejecutar sync para reintentar`);
369
+ } else if (outcome.status === "reconciled") io.out(`\u2713 ${product2}: ${outcome.changed ? "actualizado" : "al d\xEDa"}${outcome.resourceId ? ` (${outcome.resourceId})` : ""}`);
370
+ }
371
+ return failed ? 1 : 0;
372
+ } catch (error) {
373
+ io.err(`\u2717 sync: ${error instanceof Error ? error.message : error}`);
374
+ return 1;
375
+ }
376
+ }
377
+
378
+ export { SyncError, generateAppTypes, runCli, schemaToType, syncApp, validateManifest };
package/dist/index.cjs ADDED
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ var chunkL2FDW7NF_cjs = require('./chunk-L2FDW7NF.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "SyncError", {
8
+ enumerable: true,
9
+ get: function () { return chunkL2FDW7NF_cjs.SyncError; }
10
+ });
11
+ Object.defineProperty(exports, "generateAppTypes", {
12
+ enumerable: true,
13
+ get: function () { return chunkL2FDW7NF_cjs.generateAppTypes; }
14
+ });
15
+ Object.defineProperty(exports, "runCli", {
16
+ enumerable: true,
17
+ get: function () { return chunkL2FDW7NF_cjs.runCli; }
18
+ });
19
+ Object.defineProperty(exports, "schemaToType", {
20
+ enumerable: true,
21
+ get: function () { return chunkL2FDW7NF_cjs.schemaToType; }
22
+ });
23
+ Object.defineProperty(exports, "syncApp", {
24
+ enumerable: true,
25
+ get: function () { return chunkL2FDW7NF_cjs.syncApp; }
26
+ });
27
+ Object.defineProperty(exports, "validateManifest", {
28
+ enumerable: true,
29
+ get: function () { return chunkL2FDW7NF_cjs.validateManifest; }
30
+ });