@kizenapps/packager 0.3.0-18bea71 → 0.4.0-6ad8aed

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/dist/index.js CHANGED
@@ -31,26 +31,54 @@ var minifyFiles = async (files) => {
31
31
 
32
32
  // src/lib/apiNames.ts
33
33
  var sanitizeToAPIName = (name) => {
34
- return name.toLowerCase().replace(/[^a-z0-9\-\s]/g, "").replace(/\s+/g, "_");
34
+ return name.toLowerCase().replace(/[-\s]+/g, "_").replace(/[^a-z0-9_]/g, "");
35
35
  };
36
+ var resolveApiName = (explicit, directoryName) => typeof explicit === "string" && explicit !== "" ? explicit : sanitizeToAPIName(directoryName);
36
37
  var scriptRuntimeToApiName = (runtime) => {
37
38
  const result = runtime.replace(/\s+/g, "-").replace(/\./g, "-");
38
39
  return result;
39
40
  };
40
41
 
41
- // src/lib/image.ts
42
- var imagetoUint8Array = (base64Image) => {
43
- const byteCharacters = atob(base64Image);
44
- const byteNumbers = new Array(byteCharacters.length);
45
- for (let i = 0; i < byteCharacters.length; i++) {
46
- byteNumbers[i] = byteCharacters.charCodeAt(i);
42
+ // src/lib/issues.ts
43
+ var errorIssue = (rule, message, path, pluginApiName) => ({
44
+ rule,
45
+ severity: "error",
46
+ message,
47
+ ...path !== void 0 && { path },
48
+ ...pluginApiName !== void 0 && { pluginApiName }
49
+ });
50
+ var warningIssue = (rule, message, path, pluginApiName) => ({
51
+ rule,
52
+ severity: "warning",
53
+ message,
54
+ ...path !== void 0 && { path },
55
+ ...pluginApiName !== void 0 && { pluginApiName }
56
+ });
57
+ var PluginValidationError = class extends Error {
58
+ issues;
59
+ constructor(issues) {
60
+ const errors = issues.filter((i) => i.severity === "error");
61
+ const details = errors.map((i) => `${i.path ? `${i.path}: ` : ""}${i.message}`).join("\n");
62
+ super(`Plugin app validation failed with ${String(errors.length)} error(s):
63
+ ${details}`);
64
+ this.name = "PluginValidationError";
65
+ this.issues = issues;
47
66
  }
48
- return new Uint8Array(byteNumbers);
49
- };
50
- var zipToUint8Array = (zip) => {
51
- return Uint8Array.from(zip);
52
67
  };
53
68
 
69
+ // src/types/manifest.ts
70
+ var PUBLISHABLE_ENVIRONMENTS = [
71
+ "go",
72
+ "fmo",
73
+ "staging",
74
+ "integration",
75
+ "e2e-integration",
76
+ "e2e-staging",
77
+ "test1"
78
+ ];
79
+ var ENVIRONMENT_ALIASES = ["prod", "dev", "testing"];
80
+ var ENVIRONMENTS = [...PUBLISHABLE_ENVIRONMENTS, ...ENVIRONMENT_ALIASES];
81
+
54
82
  // src/lib/structure.ts
55
83
  var thumbnailImageExtensions = ["png"];
56
84
  var triggerImageExtensions = [...thumbnailImageExtensions, "svg"];
@@ -68,9 +96,7 @@ var USER_SETUP_ASSISTANT_DIRECTORY_NAME = "userSetupAssistant";
68
96
  var CALENDAR_SOURCES_DIRECTORY_NAME = "calendarSources";
69
97
  var VIEWS_DIRECTORY_NAME = "views";
70
98
  var BLOCKS_DIRECTORY_NAME = "blocks";
71
- var THUMBNAIL_FILE_NAMES = thumbnailImageExtensions.map(
72
- (ext) => `thumbnail.${ext}`
73
- );
99
+ var THUMBNAIL_FILE_NAMES = thumbnailImageExtensions.map((ext) => `thumbnail.${ext}`);
74
100
  var RELEASE_NOTES_EXTENSION = "md";
75
101
  var SCRIPT_EXTENSION = "js";
76
102
  var MAIN_SCRIPT_FILE = `script.${SCRIPT_EXTENSION}`;
@@ -82,49 +108,70 @@ var CONFIG_FILE_NAME = "config.json";
82
108
  var STYLES_FILE_NAME = "styles.css";
83
109
  var MANIFEST_FILE_NAME = "kizen.json";
84
110
  var KZN_FILE_NAME = "import.kzn";
85
- var maybeParseConfig = (content, path) => {
111
+ var tryParseConfig = (content, path, pluginApiName) => {
86
112
  if (!content) {
87
- throw new Error(`No content provided for config file: ${path}`);
113
+ return {
114
+ issue: errorIssue(
115
+ "structure/config-content",
116
+ `No content provided for config file: ${path}`,
117
+ path,
118
+ pluginApiName
119
+ )
120
+ };
88
121
  }
89
122
  try {
90
- return JSON.parse(content);
123
+ return { config: JSON.parse(content) };
91
124
  } catch {
92
- throw new Error(`Failed to parse config content: ${path}`);
125
+ return {
126
+ issue: errorIssue(
127
+ "structure/config-parse",
128
+ `Failed to parse ${CONFIG_FILE_NAME} as JSON.`,
129
+ path,
130
+ pluginApiName
131
+ )
132
+ };
93
133
  }
94
134
  };
135
+ var maybeParseConfig = (content, path, pluginApiName) => {
136
+ const result = tryParseConfig(content, path, pluginApiName);
137
+ if ("issue" in result) throw new PluginValidationError([result.issue]);
138
+ return result.config;
139
+ };
95
140
  var getMinimizedConfig = (config, entry, splitPath, consideredFiles) => {
96
- const result = config["minimized_config"] ?? {};
97
- if (result["customIconFile"]) {
141
+ const result = config.minimized_config ?? {};
142
+ const customIconFile = result.customIconFile;
143
+ if (typeof customIconFile === "string" && customIconFile) {
98
144
  let basePath = splitPath.slice(0, -1).join("/");
99
145
  if (entry.endsWith("/")) {
100
146
  basePath = `${entry}${basePath}`;
101
147
  } else {
102
148
  basePath = `${entry}/${basePath}`;
103
149
  }
104
- const filePath = `${basePath}/${result["customIconFile"]}`;
150
+ const filePath = `${basePath}/${customIconFile}`;
105
151
  const file = consideredFiles.find((f) => f.path === filePath);
106
152
  if (file?.base64Image) {
107
- let extension = filePath.split(".").pop()?.toLowerCase();
153
+ let extension = filePath.split(".").pop()?.toLowerCase() ?? "";
108
154
  if (extension === "svg") {
109
155
  extension = "svg+xml";
110
156
  }
111
- result["customIcon"] = `data:image/${extension};base64,${file.base64Image}`;
157
+ result.customIcon = `data:image/${extension};base64,${file.base64Image}`;
112
158
  }
113
159
  }
114
160
  return result;
115
161
  };
116
162
  var getAdornmentIcon = (config, entry, splitPath, consideredFiles) => {
117
- if (config["customIconFile"]) {
163
+ const customIconFile = config.customIconFile;
164
+ if (typeof customIconFile === "string" && customIconFile) {
118
165
  let basePath = splitPath.slice(0, -1).join("/");
119
166
  if (entry.endsWith("/")) {
120
167
  basePath = `${entry}${basePath}`;
121
168
  } else {
122
169
  basePath = `${entry}/${basePath}`;
123
170
  }
124
- const filePath = `${basePath}/${config["customIconFile"]}`;
171
+ const filePath = `${basePath}/${customIconFile}`;
125
172
  const file = consideredFiles.find((f) => f.path === filePath);
126
173
  if (file?.base64Image) {
127
- let extension = filePath.split(".").pop()?.toLowerCase();
174
+ let extension = filePath.split(".").pop()?.toLowerCase() ?? "";
128
175
  if (extension === "svg") {
129
176
  extension = "svg+xml";
130
177
  }
@@ -134,6 +181,411 @@ var getAdornmentIcon = (config, entry, splitPath, consideredFiles) => {
134
181
  return "";
135
182
  };
136
183
 
184
+ // src/lib/validate.ts
185
+ var API_NAME_PATTERN = /^[a-z_][a-z0-9_]+$/;
186
+ var API_NAME_HINT = "must start with a letter or underscore and contain only lowercase letters, numbers, or underscores (minimum 2 characters)";
187
+ var SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
188
+ var PATH_PATTERN = /^[a-zA-Z][a-zA-Z0-9_\-/]*$/;
189
+ var SUPPORTED_ENGINES = ["1.0.0"];
190
+ var CONFIG_REQUIRED_DIRECTORIES = [
191
+ FLOATING_FRAMES_DIRECTORY_NAME,
192
+ BLOCKS_DIRECTORY_NAME,
193
+ DATA_ADORNMENTS_DIRECTORY_NAME,
194
+ TOOLBAR_ITEMS_DIRECTORY_NAME,
195
+ ROUTABLE_PAGES_DIRECTORY_NAME,
196
+ BROWSER_ROUTE_SCRIPTS_DIRECTORY_NAME,
197
+ JS_ACTIONS_DIRECTORY_NAME,
198
+ OBJECT_SETTINGS_ITEMS_DIRECTORY_NAME,
199
+ AUTOMATION_STEPS_DIRECTORY_NAME,
200
+ CALENDAR_SOURCES_DIRECTORY_NAME
201
+ ];
202
+ var API_NAMED_DIRECTORIES = /* @__PURE__ */ new Set([
203
+ FLOATING_FRAMES_DIRECTORY_NAME,
204
+ BLOCKS_DIRECTORY_NAME,
205
+ TOOLBAR_ITEMS_DIRECTORY_NAME,
206
+ ROUTABLE_PAGES_DIRECTORY_NAME,
207
+ BROWSER_ROUTE_SCRIPTS_DIRECTORY_NAME,
208
+ JS_ACTIONS_DIRECTORY_NAME,
209
+ OBJECT_SETTINGS_ITEMS_DIRECTORY_NAME,
210
+ AUTOMATION_STEPS_DIRECTORY_NAME,
211
+ CALENDAR_SOURCES_DIRECTORY_NAME,
212
+ VIEWS_DIRECTORY_NAME
213
+ ]);
214
+ var COMPONENT_DIRECTORIES = [...CONFIG_REQUIRED_DIRECTORIES, VIEWS_DIRECTORY_NAME];
215
+ var isNonEmptyString = (value) => typeof value === "string" && value !== "";
216
+ var validateManifestFields = (manifest, label, pluginApiName) => {
217
+ const issues = [];
218
+ const problem = (rule, message) => {
219
+ issues.push(errorIssue(rule, `${label}: ${message}`, MANIFEST_FILE_NAME, pluginApiName));
220
+ };
221
+ const warn = (rule, message) => {
222
+ issues.push(warningIssue(rule, `${label}: ${message}`, MANIFEST_FILE_NAME, pluginApiName));
223
+ };
224
+ const requireString = (field) => {
225
+ const value = manifest[field];
226
+ if (!isNonEmptyString(value)) {
227
+ problem("manifest/required-field", `${field} is required and must be a non-empty string.`);
228
+ return null;
229
+ }
230
+ return value;
231
+ };
232
+ const optionalString = (field) => {
233
+ const value = manifest[field];
234
+ if (value === void 0) {
235
+ return null;
236
+ }
237
+ if (!isNonEmptyString(value)) {
238
+ problem("manifest/required-field", `${field} must be a non-empty string when present.`);
239
+ return null;
240
+ }
241
+ return value;
242
+ };
243
+ const optionalStringArray = (field, rule) => {
244
+ const value = manifest[field];
245
+ if (value === void 0) {
246
+ return null;
247
+ }
248
+ if (!Array.isArray(value) || !value.every(isNonEmptyString)) {
249
+ problem(rule, `${field} must be an array of non-empty strings when present.`);
250
+ return null;
251
+ }
252
+ return value;
253
+ };
254
+ const version = requireString("version");
255
+ if (version !== null && !SEMVER_PATTERN.test(version)) {
256
+ problem(
257
+ "manifest/version-format",
258
+ `version must be a semantic version (e.g. 1.0.0), got "${version}".`
259
+ );
260
+ }
261
+ const apiName = requireString("api_name");
262
+ if (apiName !== null && !API_NAME_PATTERN.test(apiName)) {
263
+ problem("manifest/api-name-format", `api_name "${apiName}" is invalid: ${API_NAME_HINT}.`);
264
+ }
265
+ requireString("name");
266
+ requireString("description");
267
+ const engine = requireString("engine");
268
+ if (engine !== null && !SUPPORTED_ENGINES.includes(engine)) {
269
+ problem(
270
+ "manifest/engine-version",
271
+ `engine must be one of ${SUPPORTED_ENGINES.join(", ")}, got "${engine}".`
272
+ );
273
+ }
274
+ const entry = requireString("entry");
275
+ if (entry !== null && !PATH_PATTERN.test(entry)) {
276
+ problem(
277
+ "manifest/entry-path",
278
+ `entry must be a directory path matching ${String(PATH_PATTERN)}, got "${entry}".`
279
+ );
280
+ }
281
+ const releaseNotesDirectory = optionalString("release_notes_directory");
282
+ if (releaseNotesDirectory !== null && !PATH_PATTERN.test(releaseNotesDirectory)) {
283
+ problem(
284
+ "manifest/release-notes-directory-path",
285
+ `release_notes_directory must be a directory path matching ${String(PATH_PATTERN)}, got "${releaseNotesDirectory}".`
286
+ );
287
+ }
288
+ optionalStringArray("release_branches", "manifest/release-branches");
289
+ const releaseEnvironments = optionalStringArray(
290
+ "release_environments",
291
+ "manifest/release-environments"
292
+ );
293
+ if (releaseEnvironments !== null) {
294
+ for (const env of releaseEnvironments) {
295
+ if (!ENVIRONMENTS.includes(env)) {
296
+ problem(
297
+ "manifest/release-environments",
298
+ `release_environments must only contain ${ENVIRONMENTS.join(", ")}. Instead, got "${env}".`
299
+ );
300
+ }
301
+ }
302
+ }
303
+ const developerBusinessId = manifest.developer_business_id;
304
+ if (developerBusinessId !== void 0) {
305
+ if (isNonEmptyString(developerBusinessId)) {
306
+ const distinctValidEnvs = new Set(
307
+ (releaseEnvironments ?? []).filter(
308
+ (env) => ENVIRONMENTS.includes(env)
309
+ )
310
+ );
311
+ if (distinctValidEnvs.size >= 2) {
312
+ warn(
313
+ "manifest/developer-business-id-environments",
314
+ "developer_business_id is a single id but release_environments targets multiple environments; a business id generally exists in only one environment, so publishing to the others will fail - use the per-environment object form instead."
315
+ );
316
+ }
317
+ } else if (typeof developerBusinessId === "object" && developerBusinessId !== null && !Array.isArray(developerBusinessId)) {
318
+ for (const [env, id] of Object.entries(developerBusinessId)) {
319
+ if (!PUBLISHABLE_ENVIRONMENTS.includes(env)) {
320
+ problem(
321
+ "manifest/developer-business-id",
322
+ `developer_business_id has an invalid environment key "${env}": keys must be one of ${PUBLISHABLE_ENVIRONMENTS.join(", ")}. Aliases like ${ENVIRONMENT_ALIASES.join(", ")} are not valid keys here - the publisher indexes by concrete environment, so alias keys silently resolve to nothing.`
323
+ );
324
+ }
325
+ if (!isNonEmptyString(id)) {
326
+ problem(
327
+ "manifest/developer-business-id",
328
+ `developer_business_id["${env}"] must be a non-empty string.`
329
+ );
330
+ }
331
+ }
332
+ } else {
333
+ problem(
334
+ "manifest/developer-business-id",
335
+ "developer_business_id must be a non-empty string or an object mapping publishable environments to ids."
336
+ );
337
+ }
338
+ }
339
+ return issues;
340
+ };
341
+ var collectComponents = (files, entry) => {
342
+ const entryPrefix = entry.endsWith("/") ? entry : `${entry}/`;
343
+ const components = /* @__PURE__ */ new Map();
344
+ for (const file of files) {
345
+ if (!file.path.startsWith(entryPrefix) || file.path.endsWith(".DS_Store")) {
346
+ continue;
347
+ }
348
+ const [directory, name, child] = file.path.slice(entryPrefix.length).split("/");
349
+ if (!directory || !name || !COMPONENT_DIRECTORIES.includes(directory)) {
350
+ continue;
351
+ }
352
+ const key = `${directory}/${name}`;
353
+ const existing = components.get(key) ?? {
354
+ directory,
355
+ name,
356
+ configFile: void 0
357
+ };
358
+ if (child === CONFIG_FILE_NAME) {
359
+ existing.configFile = file;
360
+ }
361
+ components.set(key, existing);
362
+ }
363
+ return [...components.values()];
364
+ };
365
+ var PAGE_LIKE_DIRECTORIES = /* @__PURE__ */ new Set([ROUTABLE_PAGES_DIRECTORY_NAME, VIEWS_DIRECTORY_NAME]);
366
+ var validateComponent = (component, entry, pluginApiName) => {
367
+ const { directory, name, configFile } = component;
368
+ const entryBase = entry.endsWith("/") ? entry.slice(0, -1) : entry;
369
+ const componentPath = `${entryBase}/${directory}/${name}`;
370
+ const issues = [];
371
+ const apiNameIssue = (value, derived2, path) => {
372
+ const derivedNote = derived2 ? ` (derived from directory name "${name}")` : "";
373
+ const derivedFix = derived2 ? ` Set an explicit api_name in ${CONFIG_FILE_NAME}.` : "";
374
+ issues.push(
375
+ errorIssue(
376
+ "structure/api-name-format",
377
+ `api_name "${value}"${derivedNote} is invalid: ${API_NAME_HINT}.${derivedFix}`,
378
+ path,
379
+ pluginApiName
380
+ )
381
+ );
382
+ };
383
+ if (!configFile) {
384
+ if (CONFIG_REQUIRED_DIRECTORIES.includes(directory)) {
385
+ issues.push(
386
+ errorIssue(
387
+ "structure/missing-config",
388
+ `Missing required ${CONFIG_FILE_NAME} for ${directory}/${name}.`,
389
+ componentPath,
390
+ pluginApiName
391
+ )
392
+ );
393
+ }
394
+ if (API_NAMED_DIRECTORIES.has(directory)) {
395
+ const derived2 = sanitizeToAPIName(name);
396
+ if (!API_NAME_PATTERN.test(derived2)) {
397
+ apiNameIssue(derived2, true, componentPath);
398
+ } else {
399
+ return {
400
+ issues,
401
+ path: componentPath,
402
+ resolved: { apiName: derived2, path: componentPath }
403
+ };
404
+ }
405
+ }
406
+ return { issues, path: componentPath };
407
+ }
408
+ const result = tryParseConfig(configFile.content, configFile.path, pluginApiName);
409
+ if ("issue" in result) {
410
+ return { issues: [result.issue], path: componentPath };
411
+ }
412
+ const config = result.config;
413
+ if (directory === FLOATING_FRAMES_DIRECTORY_NAME) {
414
+ const defaultPosition = config.default_position;
415
+ const minimizedStyle = config.minimized_style;
416
+ if ((defaultPosition === "bottom-left-fixed" || defaultPosition === "bottom-right-fixed") && minimizedStyle !== void 0 && minimizedStyle !== "circle") {
417
+ issues.push(
418
+ errorIssue(
419
+ "structure/fixed-frame-minimized-style",
420
+ `Floating frame "${name}" sets default_position "${defaultPosition}" with minimized_style ${JSON.stringify(minimizedStyle)}. Fixed-position frames require minimized_style "circle" (or omit it).`,
421
+ configFile.path,
422
+ pluginApiName
423
+ )
424
+ );
425
+ }
426
+ }
427
+ if (!API_NAMED_DIRECTORIES.has(directory)) {
428
+ return { issues, path: componentPath };
429
+ }
430
+ const explicit = config.api_name;
431
+ if (isNonEmptyString(explicit)) {
432
+ if (!API_NAME_PATTERN.test(explicit)) {
433
+ apiNameIssue(explicit, false, configFile.path);
434
+ return { issues, path: componentPath };
435
+ }
436
+ return {
437
+ issues,
438
+ path: componentPath,
439
+ resolved: { apiName: explicit, path: configFile.path }
440
+ };
441
+ }
442
+ if (explicit) {
443
+ issues.push(
444
+ errorIssue(
445
+ "structure/api-name-format",
446
+ "api_name must be a non-empty string.",
447
+ configFile.path,
448
+ pluginApiName
449
+ )
450
+ );
451
+ return { issues, path: componentPath };
452
+ }
453
+ const derived = sanitizeToAPIName(name);
454
+ if (!API_NAME_PATTERN.test(derived)) {
455
+ apiNameIssue(derived, true, configFile.path);
456
+ return { issues, path: componentPath };
457
+ }
458
+ return {
459
+ issues,
460
+ path: componentPath,
461
+ resolved: { apiName: derived, path: configFile.path }
462
+ };
463
+ };
464
+ var validateManifestList = (manifests, isMultiManifest, files) => {
465
+ const issues = [];
466
+ const seenApiNames = /* @__PURE__ */ new Map();
467
+ manifests.forEach((manifest, index) => {
468
+ const label = isMultiManifest ? `${MANIFEST_FILE_NAME} entry #${String(index + 1)}` : MANIFEST_FILE_NAME;
469
+ if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
470
+ issues.push(errorIssue("manifest/shape", `${label}: must be a JSON object.`, MANIFEST_FILE_NAME));
471
+ return;
472
+ }
473
+ const record = manifest;
474
+ const rawApiName = record.api_name;
475
+ const pluginApiName = isNonEmptyString(rawApiName) ? rawApiName : void 0;
476
+ issues.push(...validateManifestFields(record, label, pluginApiName));
477
+ if (pluginApiName !== void 0) {
478
+ const firstIndex = seenApiNames.get(pluginApiName);
479
+ if (firstIndex !== void 0) {
480
+ issues.push(
481
+ errorIssue(
482
+ "manifest/duplicate-api-name",
483
+ `${label}: duplicate api_name "${pluginApiName}" already used by entry #${String(firstIndex + 1)}. Plugins in a multi-plugin manifest must have unique api_names.`,
484
+ MANIFEST_FILE_NAME,
485
+ pluginApiName
486
+ )
487
+ );
488
+ } else {
489
+ seenApiNames.set(pluginApiName, index);
490
+ }
491
+ }
492
+ const entry = record.entry;
493
+ if (isNonEmptyString(entry) && PATH_PATTERN.test(entry)) {
494
+ const seenResolved = /* @__PURE__ */ new Map();
495
+ const seenPageNames = /* @__PURE__ */ new Map();
496
+ for (const component of collectComponents(files, entry)) {
497
+ const {
498
+ issues: componentIssues,
499
+ path: componentPath,
500
+ resolved
501
+ } = validateComponent(component, entry, pluginApiName);
502
+ issues.push(...componentIssues);
503
+ if (PAGE_LIKE_DIRECTORIES.has(component.directory)) {
504
+ const firstPage = seenPageNames.get(component.name);
505
+ if (firstPage !== void 0) {
506
+ issues.push(
507
+ errorIssue(
508
+ "structure/duplicate-component-name",
509
+ `duplicate component name "${component.name}": ${component.directory}/${component.name} (${componentPath}) collides with ${firstPage.directory}/${component.name} (${firstPage.path}). pages and views share one routable-pages collection, so names must be unique across both directories.`,
510
+ componentPath,
511
+ pluginApiName
512
+ )
513
+ );
514
+ } else {
515
+ seenPageNames.set(component.name, {
516
+ path: componentPath,
517
+ directory: component.directory
518
+ });
519
+ }
520
+ }
521
+ if (resolved) {
522
+ const key = `${component.directory}
523
+ ${resolved.apiName}`;
524
+ const firstPath = seenResolved.get(key);
525
+ if (firstPath !== void 0) {
526
+ issues.push(
527
+ errorIssue(
528
+ "structure/duplicate-api-name",
529
+ `duplicate api_name "${resolved.apiName}" in ${component.directory}: ${resolved.path} collides with ${firstPath}. Sibling ${component.directory} components must resolve to unique api_names.`,
530
+ resolved.path,
531
+ pluginApiName
532
+ )
533
+ );
534
+ } else {
535
+ seenResolved.set(key, resolved.path);
536
+ }
537
+ }
538
+ }
539
+ }
540
+ });
541
+ return issues;
542
+ };
543
+ var validatePluginApp = (files) => {
544
+ const manifestFile = files.find((f) => f.path === MANIFEST_FILE_NAME);
545
+ if (!manifestFile) {
546
+ return [
547
+ errorIssue(
548
+ "manifest/missing",
549
+ `Missing ${MANIFEST_FILE_NAME} at the root of the plugin directory.`,
550
+ MANIFEST_FILE_NAME
551
+ )
552
+ ];
553
+ }
554
+ let parsed;
555
+ try {
556
+ parsed = JSON.parse(manifestFile.content);
557
+ } catch {
558
+ return [
559
+ errorIssue(
560
+ "manifest/parse",
561
+ `${MANIFEST_FILE_NAME} could not be parsed. Is it properly formatted JSON?`,
562
+ MANIFEST_FILE_NAME
563
+ )
564
+ ];
565
+ }
566
+ const isMultiManifest = Array.isArray(parsed);
567
+ const manifests = isMultiManifest ? parsed : [parsed];
568
+ return validateManifestList(manifests, isMultiManifest, files);
569
+ };
570
+ var validatePackagedManifests = (manifests, files) => {
571
+ const isMultiManifest = Array.isArray(manifests);
572
+ const list = isMultiManifest ? manifests : [manifests];
573
+ return validateManifestList(list, isMultiManifest, files);
574
+ };
575
+
576
+ // src/lib/image.ts
577
+ var imagetoUint8Array = (base64Image) => {
578
+ const byteCharacters = atob(base64Image);
579
+ const byteNumbers = new Array(byteCharacters.length);
580
+ for (let i = 0; i < byteCharacters.length; i++) {
581
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
582
+ }
583
+ return new Uint8Array(byteNumbers);
584
+ };
585
+ var zipToUint8Array = (zip) => {
586
+ return Uint8Array.from(zip);
587
+ };
588
+
137
589
  // src/lib/package.ts
138
590
  var replacementVar = "__kizen_state";
139
591
  var prepend = "const __kizen_utils = {};\n";
@@ -146,16 +598,13 @@ return ${inner}; })()`;
146
598
  };
147
599
  var mapFields = (fields, scriptsByKey) => {
148
600
  return fields.map((field) => {
149
- if (field["type"] === "container" && field["fields"] && Array.isArray(field["fields"])) {
601
+ if (field.type === "container" && field.fields && Array.isArray(field.fields)) {
150
602
  return {
151
603
  ...field,
152
- fields: mapFields(
153
- field["fields"],
154
- scriptsByKey
155
- )
604
+ fields: mapFields(field.fields, scriptsByKey)
156
605
  };
157
606
  }
158
- const fieldKey = field["key"];
607
+ const fieldKey = field.key;
159
608
  const scripts = scriptsByKey[fieldKey];
160
609
  if (scripts) {
161
610
  const result = { ...field };
@@ -168,12 +617,7 @@ var mapFields = (fields, scriptsByKey) => {
168
617
  ]) {
169
618
  const fn = scripts[fnName];
170
619
  if (fn) {
171
- result[fnName] = convertToSelfInvokingFunction(
172
- fn,
173
- replacementVar,
174
- {},
175
- prepend
176
- );
620
+ result[fnName] = convertToSelfInvokingFunction(fn, replacementVar, {}, prepend);
177
621
  }
178
622
  }
179
623
  return result;
@@ -185,19 +629,13 @@ var processSetupAssistantFromFiles = (assistantConfig, scripts) => {
185
629
  if (!assistantConfig) {
186
630
  return void 0;
187
631
  }
188
- const scriptsByKey = scripts.reduce(
189
- (acc, script) => {
190
- (acc[script.key] ??= {})[script.type] = script.content;
191
- return acc;
192
- },
193
- {}
194
- );
632
+ const scriptsByKey = scripts.reduce((acc, script) => {
633
+ (acc[script.key] ??= {})[script.type] = script.content;
634
+ return acc;
635
+ }, {});
195
636
  const newConfig = { ...assistantConfig };
196
- if (newConfig["fields"] && Array.isArray(newConfig["fields"])) {
197
- newConfig["fields"] = mapFields(
198
- newConfig["fields"],
199
- scriptsByKey
200
- );
637
+ if (newConfig.fields && Array.isArray(newConfig.fields)) {
638
+ newConfig.fields = mapFields(newConfig.fields, scriptsByKey);
201
639
  }
202
640
  return newConfig;
203
641
  };
@@ -205,10 +643,12 @@ var processOnePluginFromManifest = (manifest, files) => {
205
643
  const entry = manifest.entry;
206
644
  const releaseNotesDirectory = manifest.release_notes_directory;
207
645
  const apiName = manifest.api_name;
646
+ const entryPrefix = entry.endsWith("/") ? entry : `${entry}/`;
208
647
  const consideredFiles = files.filter(
209
- (f) => f.path !== MANIFEST_FILE_NAME && f.path.startsWith(entry) && !f.path.endsWith(".DS_Store")
648
+ (f) => f.path !== MANIFEST_FILE_NAME && f.path.startsWith(entryPrefix) && !f.path.endsWith(".DS_Store")
210
649
  );
211
- const releaseNotesFiles = releaseNotesDirectory ? files.filter((f) => f.path.startsWith(releaseNotesDirectory)) : [];
650
+ const releaseNotesPrefix = releaseNotesDirectory?.endsWith("/") ? releaseNotesDirectory : `${releaseNotesDirectory ?? ""}/`;
651
+ const releaseNotesFiles = releaseNotesDirectory ? files.filter((f) => f.path.startsWith(releaseNotesPrefix)) : [];
212
652
  const { version } = manifest;
213
653
  const releaseNotes = releaseNotesFiles.find(
214
654
  (file) => file.path.endsWith(`${version}.${RELEASE_NOTES_EXTENSION}`)
@@ -226,18 +666,16 @@ var processOnePluginFromManifest = (manifest, files) => {
226
666
  let thumbnail = null;
227
667
  let kznFile = null;
228
668
  let hasBlockingCondition = false;
229
- const requiredConfigFiles = {};
230
669
  let setupAssistantJSON = null;
231
670
  const setupAssistantFunctions = [];
232
671
  let userSetupAssistantJSON = null;
233
672
  const userSetupAssistantFunctions = [];
234
673
  for (const file of consideredFiles) {
235
- let strippedPath = file.path.replace(entry, "");
236
- strippedPath = strippedPath.startsWith("/") ? strippedPath.substring(1) : strippedPath;
674
+ const strippedPath = file.path.slice(entryPrefix.length);
237
675
  const splitPath = strippedPath.split("/");
238
676
  if (splitPath[0] === SETUP_ASSISTANT_DIRECTORY_NAME) {
239
677
  if (splitPath[1] === "assistant.json") {
240
- setupAssistantJSON = maybeParseConfig(file.content, file.path);
678
+ setupAssistantJSON = maybeParseConfig(file.content, file.path, apiName);
241
679
  } else {
242
680
  const assistantKey = splitPath[1];
243
681
  if (assistantKey && splitPath[2]?.endsWith(`.${SCRIPT_EXTENSION}`)) {
@@ -250,7 +688,7 @@ var processOnePluginFromManifest = (manifest, files) => {
250
688
  }
251
689
  } else if (splitPath[0] === USER_SETUP_ASSISTANT_DIRECTORY_NAME) {
252
690
  if (splitPath[1] === "assistant.json") {
253
- userSetupAssistantJSON = maybeParseConfig(file.content, file.path);
691
+ userSetupAssistantJSON = maybeParseConfig(file.content, file.path, apiName);
254
692
  } else {
255
693
  const assistantKey = splitPath[1];
256
694
  if (assistantKey && splitPath[2]?.endsWith(`.${SCRIPT_EXTENSION}`)) {
@@ -266,30 +704,27 @@ var processOnePluginFromManifest = (manifest, files) => {
266
704
  if (!floatingFrameName) {
267
705
  continue;
268
706
  }
269
- if (!floatingFrames[floatingFrameName]) {
270
- requiredConfigFiles[`frame:${floatingFrameName}`] = true;
271
- floatingFrames[floatingFrameName] = {
272
- name: "",
273
- api_name: "",
274
- title: "",
275
- type: "script",
276
- css: "",
277
- event_scripts: {},
278
- default_position: "bottom-right",
279
- header_color: "",
280
- header_text_color: "",
281
- height: 0,
282
- width: 0,
283
- ignore: [],
284
- match: [],
285
- message_handler: "",
286
- minimized_style: "circle",
287
- minimized_config: {},
288
- script: "",
289
- html: "",
290
- when: ""
291
- };
292
- }
707
+ floatingFrames[floatingFrameName] ??= {
708
+ name: "",
709
+ api_name: "",
710
+ title: "",
711
+ type: "script",
712
+ css: "",
713
+ event_scripts: {},
714
+ default_position: "bottom-right",
715
+ header_color: "",
716
+ header_text_color: "",
717
+ height: 0,
718
+ width: 0,
719
+ ignore: [],
720
+ match: [],
721
+ message_handler: "",
722
+ minimized_style: "circle",
723
+ minimized_config: {},
724
+ script: "",
725
+ html: "",
726
+ when: ""
727
+ };
293
728
  const frame = floatingFrames[floatingFrameName];
294
729
  if (splitPath[2] === EVENT_SCRIPTS_DIRECTORY_NAME) {
295
730
  if (splitPath[3]?.endsWith(`.${SCRIPT_EXTENSION}`)) {
@@ -297,27 +732,22 @@ var processOnePluginFromManifest = (manifest, files) => {
297
732
  frame.event_scripts[functionName] = file.minified;
298
733
  }
299
734
  } else if (splitPath[2] === CONFIG_FILE_NAME) {
300
- requiredConfigFiles[`frame:${floatingFrameName}`] = false;
301
- const config = maybeParseConfig(file.content, file.path);
302
- frame.name = config["name"] || "";
303
- frame.api_name = config["api_name"] || sanitizeToAPIName(floatingFrameName);
304
- frame.title = config["title"] || "";
305
- frame.default_position = config["default_position"] || "bottom-right";
306
- frame.header_color = config["header_color"] || "";
307
- frame.header_text_color = config["header_text_color"] || "";
308
- frame.height = config["height"] || 0;
309
- frame.width = config["width"] || 0;
310
- frame.ignore = config["ignore"] || [];
311
- frame.match = config["match"] || [];
312
- frame.html = config["html"] || "";
313
- frame.minimized_style = config["minimized_style"] || "circle";
314
- frame.minimized_config = getMinimizedConfig(
315
- config,
316
- entry,
317
- splitPath,
318
- consideredFiles
319
- );
320
- frame.when = config["when"] || "";
735
+ const config = maybeParseConfig(file.content, file.path, apiName);
736
+ frame.name = config.name || "";
737
+ frame.api_name = resolveApiName(config.api_name, floatingFrameName);
738
+ frame.title = config.title || "";
739
+ frame.default_position = config.default_position || "bottom-right";
740
+ frame.header_color = config.header_color || "";
741
+ frame.header_text_color = config.header_text_color || "";
742
+ frame.height = config.height || 0;
743
+ frame.width = config.width || 0;
744
+ frame.ignore = Array.isArray(config.ignore) ? config.ignore : [];
745
+ frame.match = Array.isArray(config.match) ? config.match : [];
746
+ frame.html = config.html || "";
747
+ const minimizedStyle = config.minimized_style;
748
+ frame.minimized_style = minimizedStyle === "bar" || minimizedStyle === "circle" || minimizedStyle === "none" ? minimizedStyle : "circle";
749
+ frame.minimized_config = getMinimizedConfig(config, entry, splitPath, consideredFiles);
750
+ frame.when = config.when || "";
321
751
  } else if (splitPath[2] === MESSAGE_SCRIPT_FILE) {
322
752
  frame.message_handler = file.minified;
323
753
  } else if (splitPath[2] === MAIN_SCRIPT_FILE) {
@@ -330,21 +760,18 @@ var processOnePluginFromManifest = (manifest, files) => {
330
760
  if (!blockName) {
331
761
  continue;
332
762
  }
333
- if (!blocks[blockName]) {
334
- requiredConfigFiles[`block:${blockName}`] = true;
335
- blocks[blockName] = {
336
- name: "",
337
- api_name: "",
338
- min_w: 1,
339
- max_w: 12,
340
- min_h: 1,
341
- max_h: 12,
342
- event_scripts: {},
343
- script: "",
344
- styles: "",
345
- when: ""
346
- };
347
- }
763
+ blocks[blockName] ??= {
764
+ name: "",
765
+ api_name: "",
766
+ min_w: 1,
767
+ max_w: 12,
768
+ min_h: 1,
769
+ max_h: 12,
770
+ event_scripts: {},
771
+ script: "",
772
+ styles: "",
773
+ when: ""
774
+ };
348
775
  const block = blocks[blockName];
349
776
  if (splitPath[2] === EVENT_SCRIPTS_DIRECTORY_NAME) {
350
777
  if (splitPath[3]?.endsWith(`.${SCRIPT_EXTENSION}`)) {
@@ -352,22 +779,21 @@ var processOnePluginFromManifest = (manifest, files) => {
352
779
  block.event_scripts[functionName] = file.minified;
353
780
  }
354
781
  } else if (splitPath[2] === CONFIG_FILE_NAME) {
355
- requiredConfigFiles[`block:${blockName}`] = false;
356
- const config = maybeParseConfig(file.content, file.path);
357
- block.name = config["name"] || "";
358
- block.api_name = config["api_name"] || sanitizeToAPIName(blockName);
359
- block.min_w = config["min_w"] || 1;
360
- block.max_w = config["max_w"] || 12;
361
- block.min_h = config["min_h"] || 1;
362
- block.max_h = config["max_h"] || 12;
363
- if (config["recommended_height"] != null) {
364
- block.recommended_height = config["recommended_height"];
782
+ const config = maybeParseConfig(file.content, file.path, apiName);
783
+ block.name = config.name || "";
784
+ block.api_name = resolveApiName(config.api_name, blockName);
785
+ block.min_w = config.min_w || 1;
786
+ block.max_w = config.max_w || 12;
787
+ block.min_h = config.min_h || 1;
788
+ block.max_h = config.max_h || 12;
789
+ if (config.recommended_height != null) {
790
+ block.recommended_height = config.recommended_height;
365
791
  }
366
- if (config["types"]) {
367
- block.types = config["types"];
792
+ if (config.types) {
793
+ block.types = config.types;
368
794
  }
369
- block.when = config["when"] || "";
370
- if (config["when"]) {
795
+ block.when = config.when || "";
796
+ if (config.when) {
371
797
  hasBlockingCondition = true;
372
798
  }
373
799
  } else if (splitPath[2] === MAIN_SCRIPT_FILE) {
@@ -380,31 +806,23 @@ var processOnePluginFromManifest = (manifest, files) => {
380
806
  if (!dataAdornmentName) {
381
807
  continue;
382
808
  }
383
- if (!dataAdornments[dataAdornmentName]) {
384
- requiredConfigFiles[`dataAdornment:${dataAdornmentName}`] = true;
385
- dataAdornments[dataAdornmentName] = {
386
- config: { icon: "", color: "", tooltip: "" },
387
- field_type: "phonenumber",
388
- script: "",
389
- when: ""
390
- };
391
- }
809
+ dataAdornments[dataAdornmentName] ??= {
810
+ config: { icon: "", color: "", tooltip: "" },
811
+ field_type: "phonenumber",
812
+ script: "",
813
+ when: ""
814
+ };
392
815
  const adornment = dataAdornments[dataAdornmentName];
393
816
  if (splitPath[2] === CONFIG_FILE_NAME) {
394
- requiredConfigFiles[`dataAdornment:${dataAdornmentName}`] = false;
395
- const config = maybeParseConfig(file.content, file.path);
396
- adornment.config.icon = config["icon"] || "";
397
- adornment.config.color = config["color"] || "";
398
- adornment.config.tooltip = config["tooltip"] || "";
399
- adornment.config.customIcon = getAdornmentIcon(
400
- config,
401
- entry,
402
- splitPath,
403
- consideredFiles
404
- );
405
- adornment.field_type = config["field_type"] || "phonenumber";
406
- adornment.when = config["when"] || "";
407
- if (config["when"]) hasBlockingCondition = true;
817
+ const config = maybeParseConfig(file.content, file.path, apiName);
818
+ adornment.config.icon = config.icon || "";
819
+ adornment.config.color = config.color || "";
820
+ adornment.config.tooltip = config.tooltip || "";
821
+ adornment.config.customIcon = getAdornmentIcon(config, entry, splitPath, consideredFiles);
822
+ const fieldType = config.field_type;
823
+ adornment.field_type = fieldType === "phonenumber" || fieldType === "date" || fieldType === "datetime" ? fieldType : "phonenumber";
824
+ adornment.when = config.when || "";
825
+ if (config.when) hasBlockingCondition = true;
408
826
  } else if (splitPath[2] === MAIN_SCRIPT_FILE) {
409
827
  adornment.script = file.minified;
410
828
  }
@@ -413,27 +831,23 @@ var processOnePluginFromManifest = (manifest, files) => {
413
831
  if (!toolbarItemName) {
414
832
  continue;
415
833
  }
416
- if (!toolbarItems[toolbarItemName]) {
417
- requiredConfigFiles[`toolbarItem:${toolbarItemName}`] = true;
418
- toolbarItems[toolbarItemName] = {
419
- api_name: "",
420
- label: "",
421
- icon: "",
422
- color: "",
423
- script: "",
424
- when: ""
425
- };
426
- }
834
+ toolbarItems[toolbarItemName] ??= {
835
+ api_name: "",
836
+ label: "",
837
+ icon: "",
838
+ color: "",
839
+ script: "",
840
+ when: ""
841
+ };
427
842
  const item = toolbarItems[toolbarItemName];
428
843
  if (splitPath[2] === CONFIG_FILE_NAME) {
429
- requiredConfigFiles[`toolbarItem:${toolbarItemName}`] = false;
430
- const config = maybeParseConfig(file.content, file.path);
431
- item.api_name = config["api_name"] || sanitizeToAPIName(toolbarItemName);
432
- item.label = config["label"] || "";
433
- item.icon = config["icon"] || "";
434
- item.color = config["color"] || "";
435
- item.when = config["when"] || "";
436
- if (config["when"]) hasBlockingCondition = true;
844
+ const config = maybeParseConfig(file.content, file.path, apiName);
845
+ item.api_name = resolveApiName(config.api_name, toolbarItemName);
846
+ item.label = config.label || "";
847
+ item.icon = config.icon || "";
848
+ item.color = config.color || "";
849
+ item.when = config.when || "";
850
+ if (config.when) hasBlockingCondition = true;
437
851
  } else if (splitPath[2] === MAIN_SCRIPT_FILE) {
438
852
  item.script = file.minified;
439
853
  }
@@ -442,23 +856,20 @@ var processOnePluginFromManifest = (manifest, files) => {
442
856
  if (!pageName) {
443
857
  continue;
444
858
  }
445
- if (!routablePages[pageName]) {
446
- requiredConfigFiles[`page:${pageName}`] = true;
447
- routablePages[pageName] = {
448
- name: pageName,
449
- api_name: "",
450
- event_scripts: {},
451
- script: "",
452
- callback: "",
453
- css: "",
454
- is_toolbar_item: false,
455
- toolbar_color: "",
456
- toolbar_icon: "",
457
- type: "script",
458
- html: "",
459
- iframe_url: ""
460
- };
461
- }
859
+ routablePages[pageName] ??= {
860
+ name: pageName,
861
+ api_name: "",
862
+ event_scripts: {},
863
+ script: "",
864
+ callback: "",
865
+ css: "",
866
+ is_toolbar_item: false,
867
+ toolbar_color: "",
868
+ toolbar_icon: "",
869
+ type: "script",
870
+ html: "",
871
+ iframe_url: ""
872
+ };
462
873
  const page = routablePages[pageName];
463
874
  if (splitPath[2] === EVENT_SCRIPTS_DIRECTORY_NAME) {
464
875
  if (splitPath[3]?.endsWith(SCRIPT_EXTENSION)) {
@@ -466,13 +877,12 @@ var processOnePluginFromManifest = (manifest, files) => {
466
877
  page.event_scripts[functionName] = file.minified;
467
878
  }
468
879
  } else if (splitPath[2] === CONFIG_FILE_NAME) {
469
- requiredConfigFiles[`page:${pageName}`] = false;
470
- const config = maybeParseConfig(file.content, file.path);
471
- page.api_name = config["api_name"] || sanitizeToAPIName(pageName);
472
- page.name = config["name"] || pageName;
473
- page.is_toolbar_item = config["is_toolbar_item"] || false;
474
- page.toolbar_color = config["toolbar_color"] || "";
475
- page.toolbar_icon = config["toolbar_icon"] || "";
880
+ const config = maybeParseConfig(file.content, file.path, apiName);
881
+ page.api_name = resolveApiName(config.api_name, pageName);
882
+ page.name = config.name || pageName;
883
+ page.is_toolbar_item = config.is_toolbar_item || false;
884
+ page.toolbar_color = config.toolbar_color || "";
885
+ page.toolbar_icon = config.toolbar_icon || "";
476
886
  } else if (splitPath[2] === MAIN_SCRIPT_FILE) {
477
887
  page.script = file.minified;
478
888
  } else if (splitPath[2] === CALLBACK_SCRIPT_FILE) {
@@ -485,39 +895,35 @@ var processOnePluginFromManifest = (manifest, files) => {
485
895
  if (!stepName) {
486
896
  continue;
487
897
  }
488
- if (!automationSteps[stepName]) {
489
- requiredConfigFiles[`automationStep:${stepName}`] = true;
490
- automationSteps[stepName] = {
491
- name: "",
492
- action_step_api_name: "",
493
- overall_description: "",
494
- action_description: "",
495
- action_type: "",
496
- script_runtime: scriptRuntimeToApiName("python 3.12"),
497
- secrets: [],
498
- inputs: [],
499
- outputs: [],
500
- script: "",
501
- when: ""
502
- };
503
- }
898
+ automationSteps[stepName] ??= {
899
+ name: "",
900
+ action_step_api_name: "",
901
+ overall_description: "",
902
+ action_description: "",
903
+ action_type: "",
904
+ script_runtime: scriptRuntimeToApiName("python 3.12"),
905
+ secrets: [],
906
+ inputs: [],
907
+ outputs: [],
908
+ script: "",
909
+ when: ""
910
+ };
504
911
  const step = automationSteps[stepName];
505
912
  if (splitPath[2] === CONFIG_FILE_NAME) {
506
- requiredConfigFiles[`automationStep:${stepName}`] = false;
507
- const config = maybeParseConfig(file.content, file.path);
508
- step.name = config["name"] || stepName;
509
- step.action_step_api_name = config["api_name"] || sanitizeToAPIName(stepName);
510
- step.overall_description = config["plugin_description"] || "";
511
- step.action_description = config["action_description"] || "";
512
- step.action_type = config["action_type"] || "";
513
- step.secrets = config["secrets"] || [];
514
- step.inputs = config["inputs"] || [];
515
- step.outputs = config["outputs"] || [];
913
+ const config = maybeParseConfig(file.content, file.path, apiName);
914
+ step.name = config.name || stepName;
915
+ step.action_step_api_name = resolveApiName(config.api_name, stepName);
916
+ step.overall_description = config.plugin_description || "";
917
+ step.action_description = config.action_description || "";
918
+ step.action_type = config.action_type || "";
919
+ step.secrets = Array.isArray(config.secrets) ? config.secrets : [];
920
+ step.inputs = Array.isArray(config.inputs) ? config.inputs : [];
921
+ step.outputs = Array.isArray(config.outputs) ? config.outputs : [];
516
922
  step.script_runtime = scriptRuntimeToApiName(
517
- config["runtime"] || "python 3.12"
923
+ config.runtime || "python 3.12"
518
924
  );
519
- step.when = config["when"] || "";
520
- if (config["when"]) hasBlockingCondition = true;
925
+ step.when = config.when || "";
926
+ if (config.when) hasBlockingCondition = true;
521
927
  } else if (splitPath[2] === AUTOMATION_SCRIPT_FILE) {
522
928
  step.script = file.content;
523
929
  }
@@ -526,24 +932,20 @@ var processOnePluginFromManifest = (manifest, files) => {
526
932
  if (!routeName) {
527
933
  continue;
528
934
  }
529
- if (!routeScripts[routeName]) {
530
- requiredConfigFiles[`route:${routeName}`] = true;
531
- routeScripts[routeName] = {
532
- script: "",
533
- api_name: "",
534
- hint_object_name: "",
535
- routes: [],
536
- name: routeName
537
- };
538
- }
935
+ routeScripts[routeName] ??= {
936
+ script: "",
937
+ api_name: "",
938
+ hint_object_name: "",
939
+ routes: [],
940
+ name: routeName
941
+ };
539
942
  const route = routeScripts[routeName];
540
943
  if (splitPath[2] === CONFIG_FILE_NAME) {
541
- requiredConfigFiles[`route:${routeName}`] = false;
542
- const config = maybeParseConfig(file.content, file.path);
543
- route.api_name = config["api_name"] || sanitizeToAPIName(routeName);
544
- route.hint_object_name = config["hint_object_name"] || "";
545
- route.routes = config["routes"] || [];
546
- route.name = config["name"] || routeName;
944
+ const config = maybeParseConfig(file.content, file.path, apiName);
945
+ route.api_name = resolveApiName(config.api_name, routeName);
946
+ route.hint_object_name = config.hint_object_name || "";
947
+ route.routes = Array.isArray(config.routes) ? config.routes : [];
948
+ route.name = config.name || routeName;
547
949
  } else if (splitPath[2] === MAIN_SCRIPT_FILE) {
548
950
  route.script = file.minified;
549
951
  }
@@ -552,22 +954,18 @@ var processOnePluginFromManifest = (manifest, files) => {
552
954
  if (!actionName) {
553
955
  continue;
554
956
  }
555
- if (!jsActions[actionName]) {
556
- requiredConfigFiles[`action:${actionName}`] = true;
557
- jsActions[actionName] = {
558
- script: "",
559
- name: "",
560
- hint_object_name: "",
561
- api_name: ""
562
- };
563
- }
957
+ jsActions[actionName] ??= {
958
+ script: "",
959
+ name: "",
960
+ hint_object_name: "",
961
+ api_name: ""
962
+ };
564
963
  const action = jsActions[actionName];
565
964
  if (splitPath[2] === CONFIG_FILE_NAME) {
566
- requiredConfigFiles[`action:${actionName}`] = false;
567
- const config = maybeParseConfig(file.content, file.path);
568
- action.name = config["name"] || actionName;
569
- action.hint_object_name = config["hint_object_name"] || "";
570
- action.api_name = config["api_name"] || sanitizeToAPIName(actionName);
965
+ const config = maybeParseConfig(file.content, file.path, apiName);
966
+ action.name = config.name || actionName;
967
+ action.hint_object_name = config.hint_object_name || "";
968
+ action.api_name = resolveApiName(config.api_name, actionName);
571
969
  } else if (splitPath[2] === MAIN_SCRIPT_FILE) {
572
970
  action.script = file.minified;
573
971
  }
@@ -576,22 +974,18 @@ var processOnePluginFromManifest = (manifest, files) => {
576
974
  if (!itemName) {
577
975
  continue;
578
976
  }
579
- if (!objectSettingsItems[itemName]) {
580
- requiredConfigFiles[`objectSettingsItem:${itemName}`] = true;
581
- objectSettingsItems[itemName] = {
582
- api_name: "",
583
- label: "",
584
- script: "",
585
- when: ""
586
- };
587
- }
977
+ objectSettingsItems[itemName] ??= {
978
+ api_name: "",
979
+ label: "",
980
+ script: "",
981
+ when: ""
982
+ };
588
983
  const settingsItem = objectSettingsItems[itemName];
589
984
  if (splitPath[2] === CONFIG_FILE_NAME) {
590
- requiredConfigFiles[`objectSettingsItem:${itemName}`] = false;
591
- const config = maybeParseConfig(file.content, file.path);
592
- settingsItem.api_name = config["api_name"] || sanitizeToAPIName(itemName);
593
- settingsItem.label = config["label"] || "";
594
- settingsItem.when = config["when"] || "";
985
+ const config = maybeParseConfig(file.content, file.path, apiName);
986
+ settingsItem.api_name = resolveApiName(config.api_name, itemName);
987
+ settingsItem.label = config.label || "";
988
+ settingsItem.when = config.when || "";
595
989
  } else if (splitPath[2] === MAIN_SCRIPT_FILE) {
596
990
  settingsItem.script = file.minified;
597
991
  }
@@ -600,24 +994,20 @@ var processOnePluginFromManifest = (manifest, files) => {
600
994
  if (!itemName) {
601
995
  continue;
602
996
  }
603
- if (!calendarSources[itemName]) {
604
- requiredConfigFiles[`calendarSource:${itemName}`] = true;
605
- calendarSources[itemName] = {
606
- api_name: "",
607
- name: "",
608
- calendars_script: "",
609
- events_script: "",
610
- when: ""
611
- };
612
- }
997
+ calendarSources[itemName] ??= {
998
+ api_name: "",
999
+ name: "",
1000
+ calendars_script: "",
1001
+ events_script: "",
1002
+ when: ""
1003
+ };
613
1004
  const cal = calendarSources[itemName];
614
1005
  if (splitPath[2] === CONFIG_FILE_NAME) {
615
- requiredConfigFiles[`calendarSource:${itemName}`] = false;
616
- const config = maybeParseConfig(file.content, file.path);
617
- cal.api_name = config["api_name"] || sanitizeToAPIName(itemName);
618
- cal.name = config["name"] || "";
619
- cal.when = config["when"] || "";
620
- if (config["when"]) hasBlockingCondition = true;
1006
+ const config = maybeParseConfig(file.content, file.path, apiName);
1007
+ cal.api_name = resolveApiName(config.api_name, itemName);
1008
+ cal.name = config.name || "";
1009
+ cal.when = config.when || "";
1010
+ if (config.when) hasBlockingCondition = true;
621
1011
  } else if (splitPath[2] === "calendars.js") {
622
1012
  cal.calendars_script = file.minified;
623
1013
  } else if (splitPath[2] === "events.js") {
@@ -628,22 +1018,20 @@ var processOnePluginFromManifest = (manifest, files) => {
628
1018
  if (!pageName) {
629
1019
  continue;
630
1020
  }
631
- if (!routablePages[pageName]) {
632
- routablePages[pageName] = {
633
- name: pageName,
634
- api_name: sanitizeToAPIName(pageName),
635
- event_scripts: {},
636
- script: "",
637
- callback: "",
638
- css: "",
639
- is_toolbar_item: false,
640
- toolbar_color: "",
641
- toolbar_icon: "",
642
- type: "html",
643
- html: "",
644
- iframe_url: ""
645
- };
646
- }
1021
+ routablePages[pageName] ??= {
1022
+ name: pageName,
1023
+ api_name: sanitizeToAPIName(pageName),
1024
+ event_scripts: {},
1025
+ script: "",
1026
+ callback: "",
1027
+ css: "",
1028
+ is_toolbar_item: false,
1029
+ toolbar_color: "",
1030
+ toolbar_icon: "",
1031
+ type: "html",
1032
+ html: "",
1033
+ iframe_url: ""
1034
+ };
647
1035
  const page = routablePages[pageName];
648
1036
  if (splitPath[2] === EVENT_SCRIPTS_DIRECTORY_NAME) {
649
1037
  if (splitPath[3]?.endsWith(SCRIPT_EXTENSION)) {
@@ -651,9 +1039,9 @@ var processOnePluginFromManifest = (manifest, files) => {
651
1039
  page.event_scripts[functionName] = file.minified;
652
1040
  }
653
1041
  } else if (splitPath[2] === CONFIG_FILE_NAME) {
654
- const config = maybeParseConfig(file.content, file.path);
655
- page.api_name = config["api_name"] || sanitizeToAPIName(pageName);
656
- page.name = config["name"] || pageName;
1042
+ const config = maybeParseConfig(file.content, file.path, apiName);
1043
+ page.api_name = resolveApiName(config.api_name, pageName);
1044
+ page.name = config.name || pageName;
657
1045
  } else if (splitPath[2] === VIEW_FILE) {
658
1046
  page.html = file.content;
659
1047
  page.type = "html";
@@ -665,35 +1053,42 @@ var processOnePluginFromManifest = (manifest, files) => {
665
1053
  }
666
1054
  } else if (splitPath[0] === KZN_FILE_NAME) {
667
1055
  if (!file.binaryData) {
668
- throw new Error(
669
- `KZN file ${file.path} does not contain binary data. Cannot proceed.`
670
- );
1056
+ throw new PluginValidationError([
1057
+ errorIssue(
1058
+ "structure/kzn-binary",
1059
+ `KZN file ${file.path} does not contain binary data. Cannot proceed.`,
1060
+ file.path,
1061
+ apiName
1062
+ )
1063
+ ]);
671
1064
  }
672
1065
  if (kznFile) {
673
- throw new Error(
674
- `Multiple KZN files found for plugin ${apiName}. Only one is allowed.`
675
- );
1066
+ throw new PluginValidationError([
1067
+ errorIssue(
1068
+ "structure/duplicate-kzn",
1069
+ `Multiple KZN files found for plugin ${apiName}. Only one is allowed.`,
1070
+ file.path,
1071
+ apiName
1072
+ )
1073
+ ]);
676
1074
  }
677
1075
  kznFile = zipToUint8Array(file.binaryData);
678
1076
  } else if (THUMBNAIL_FILE_NAMES.includes(splitPath[0] ?? "")) {
679
1077
  if (file.base64Image) {
680
1078
  if (thumbnail) {
681
- throw new Error(
682
- `Multiple thumbnail files found for plugin ${apiName}. Only one is allowed.`
683
- );
1079
+ throw new PluginValidationError([
1080
+ errorIssue(
1081
+ "structure/duplicate-thumbnail",
1082
+ `Multiple thumbnail files found for plugin ${apiName}. Only one is allowed.`,
1083
+ file.path,
1084
+ apiName
1085
+ )
1086
+ ]);
684
1087
  }
685
1088
  thumbnail = imagetoUint8Array(file.base64Image);
686
1089
  }
687
1090
  }
688
1091
  }
689
- const requiredFileKeys = Object.keys(requiredConfigFiles).filter(
690
- (key) => requiredConfigFiles[key] === true
691
- );
692
- if (requiredFileKeys.length > 0) {
693
- throw new Error(
694
- `The following required files are missing for plugin ${apiName}: ${requiredFileKeys.join(" config.json, ")} config.json. Please ensure these files are present in the repository.`
695
- );
696
- }
697
1092
  const setupAssistantContent = processSetupAssistantFromFiles(
698
1093
  setupAssistantJSON,
699
1094
  setupAssistantFunctions
@@ -714,26 +1109,31 @@ var processOnePluginFromManifest = (manifest, files) => {
714
1109
  if (resolvedUserSetupAssistant !== void 0) {
715
1110
  updatedManifest.user_setup_assistant = resolvedUserSetupAssistant;
716
1111
  }
717
- return {
718
- [apiName]: {
719
- manifest: updatedManifest,
720
- thumbnail,
721
- floatingFrames,
722
- blocks,
723
- dataAdornments,
724
- toolbarItems,
725
- routablePages,
726
- actions: jsActions,
727
- routeScripts,
728
- releaseNotes: releaseNotes?.content,
729
- objectSettingsMenuItems: objectSettingsItems,
730
- calendarSources,
731
- automationSteps,
732
- kznFile
733
- }
1112
+ const packagedPlugin = {
1113
+ manifest: updatedManifest,
1114
+ thumbnail,
1115
+ floatingFrames,
1116
+ blocks,
1117
+ dataAdornments,
1118
+ toolbarItems,
1119
+ routablePages,
1120
+ actions: jsActions,
1121
+ routeScripts,
1122
+ objectSettingsMenuItems: objectSettingsItems,
1123
+ calendarSources,
1124
+ automationSteps,
1125
+ kznFile
734
1126
  };
1127
+ if (releaseNotes?.content !== void 0) {
1128
+ packagedPlugin.releaseNotes = releaseNotes.content;
1129
+ }
1130
+ return { [apiName]: packagedPlugin };
735
1131
  };
736
1132
  var packagePlugin = (files, manifests) => {
1133
+ const errors = validatePackagedManifests(manifests, files).filter((i) => i.severity === "error");
1134
+ if (errors.length > 0) {
1135
+ throw new PluginValidationError(errors);
1136
+ }
737
1137
  const manifestArray = Array.isArray(manifests) ? manifests : [manifests];
738
1138
  return manifestArray.reduce((acc, manifest) => {
739
1139
  return { ...acc, ...processOnePluginFromManifest(manifest, files) };
@@ -742,12 +1142,24 @@ var packagePlugin = (files, manifests) => {
742
1142
  var parseManifestFromFiles = (files) => {
743
1143
  const manifestFile = files.find((f) => f.path === MANIFEST_FILE_NAME);
744
1144
  if (!manifestFile) {
745
- throw new Error("Missing kizen.json at the root of the plugin directory.");
1145
+ throw new PluginValidationError([
1146
+ errorIssue(
1147
+ "manifest/missing",
1148
+ `Missing ${MANIFEST_FILE_NAME} at the root of the plugin directory.`,
1149
+ MANIFEST_FILE_NAME
1150
+ )
1151
+ ]);
746
1152
  }
747
1153
  try {
748
1154
  return JSON.parse(manifestFile.content);
749
1155
  } catch {
750
- throw new Error("kizen.json could not be parsed.");
1156
+ throw new PluginValidationError([
1157
+ errorIssue(
1158
+ "manifest/parse",
1159
+ `${MANIFEST_FILE_NAME} could not be parsed. Is it properly formatted JSON?`,
1160
+ MANIFEST_FILE_NAME
1161
+ )
1162
+ ]);
751
1163
  }
752
1164
  };
753
1165
 
@@ -769,9 +1181,14 @@ var formatBaseConfig = (packagedPlugin) => {
769
1181
  actions: (assistantConfig.actions ?? []).map((apiName) => {
770
1182
  const action = actionsByApiName[apiName];
771
1183
  if (!action) {
772
- throw new Error(
773
- `Action with API name ${apiName} not found in packaged plugin for setup assistant. Allowed actions: ${Object.keys(actionsByApiName).join(", ")}`
774
- );
1184
+ throw new PluginValidationError([
1185
+ errorIssue(
1186
+ "structure/setup-assistant-action-ref",
1187
+ `Action with API name ${apiName} not found in packaged plugin for setup assistant. Allowed actions: ${Object.keys(actionsByApiName).join(", ")}`,
1188
+ void 0,
1189
+ packagedPlugin.manifest.api_name
1190
+ )
1191
+ ]);
775
1192
  }
776
1193
  return action;
777
1194
  })
@@ -797,7 +1214,7 @@ var transformDeployablePlugin = (packagedPlugin) => {
797
1214
  calendarSources,
798
1215
  kznFile
799
1216
  } = packagedPlugin;
800
- return {
1217
+ const deployablePlugin = {
801
1218
  ...manifest,
802
1219
  api_name: manifest.api_name,
803
1220
  artifacts: {
@@ -814,11 +1231,14 @@ var transformDeployablePlugin = (packagedPlugin) => {
814
1231
  },
815
1232
  thumbnail,
816
1233
  kznFile,
817
- releaseNotes,
818
1234
  base_config: formatBaseConfig(packagedPlugin),
819
1235
  setup_assistant: void 0,
820
1236
  services: manifest.services ?? []
821
1237
  };
1238
+ if (releaseNotes !== void 0) {
1239
+ deployablePlugin.releaseNotes = releaseNotes;
1240
+ }
1241
+ return deployablePlugin;
822
1242
  };
823
1243
 
824
1244
  // src/lib/crypto.ts
@@ -869,15 +1289,8 @@ var encrypt = (plaintext, publicKeyPem) => {
869
1289
  };
870
1290
  };
871
1291
  var decrypt = (envelope, privateKeyPem) => {
872
- const aesKey = privateDecrypt(
873
- { key: privateKeyPem, ...OAEP },
874
- Buffer.from(envelope.k, "base64")
875
- );
876
- const decipher = createDecipheriv(
877
- "aes-256-gcm",
878
- aesKey,
879
- Buffer.from(envelope.iv, "base64")
880
- );
1292
+ const aesKey = privateDecrypt({ key: privateKeyPem, ...OAEP }, Buffer.from(envelope.k, "base64"));
1293
+ const decipher = createDecipheriv("aes-256-gcm", aesKey, Buffer.from(envelope.iv, "base64"));
881
1294
  decipher.setAuthTag(Buffer.from(envelope.tag, "base64"));
882
1295
  return Buffer.concat([
883
1296
  decipher.update(Buffer.from(envelope.ct, "base64")),
@@ -918,23 +1331,22 @@ var deserializeEnvelope = (value) => {
918
1331
  const tagBytes = Buffer.from(envelope.tag, "base64");
919
1332
  const kBytes = Buffer.from(envelope.k, "base64");
920
1333
  if (ivBytes.length !== 12) {
921
- throw new Error(
922
- `Invalid encrypted value: iv must be 12 bytes (got ${ivBytes.length})`
923
- );
1334
+ throw new Error(`Invalid encrypted value: iv must be 12 bytes (got ${String(ivBytes.length)})`);
924
1335
  }
925
1336
  if (tagBytes.length !== 16) {
926
1337
  throw new Error(
927
- `Invalid encrypted value: tag must be 16 bytes (got ${tagBytes.length})`
1338
+ `Invalid encrypted value: tag must be 16 bytes (got ${String(tagBytes.length)})`
928
1339
  );
929
1340
  }
930
1341
  if (kBytes.length !== 384) {
931
1342
  throw new Error(
932
- `Invalid encrypted value: wrapped key must be 384 bytes (got ${kBytes.length})`
1343
+ `Invalid encrypted value: wrapped key must be 384 bytes (got ${String(kBytes.length)})`
933
1344
  );
934
1345
  }
935
1346
  return envelope;
936
1347
  };
937
1348
  export {
1349
+ API_NAME_PATTERN,
938
1350
  AUTOMATION_SCRIPT_FILE,
939
1351
  AUTOMATION_STEPS_DIRECTORY_NAME,
940
1352
  BLOCKS_DIRECTORY_NAME,
@@ -945,6 +1357,8 @@ export {
945
1357
  CRYPTO_ALG,
946
1358
  CRYPTO_VERSION,
947
1359
  DATA_ADORNMENTS_DIRECTORY_NAME,
1360
+ ENVIRONMENTS,
1361
+ ENVIRONMENT_ALIASES,
948
1362
  EVENT_SCRIPTS_DIRECTORY_NAME,
949
1363
  FLOATING_FRAMES_DIRECTORY_NAME,
950
1364
  JS_ACTIONS_DIRECTORY_NAME,
@@ -953,6 +1367,8 @@ export {
953
1367
  MANIFEST_FILE_NAME,
954
1368
  MESSAGE_SCRIPT_FILE,
955
1369
  OBJECT_SETTINGS_ITEMS_DIRECTORY_NAME,
1370
+ PUBLISHABLE_ENVIRONMENTS,
1371
+ PluginValidationError,
956
1372
  RELEASE_NOTES_EXTENSION,
957
1373
  ROUTABLE_PAGES_DIRECTORY_NAME,
958
1374
  SCRIPT_EXTENSION,
@@ -964,6 +1380,7 @@ export {
964
1380
  decrypt,
965
1381
  deserializeEnvelope,
966
1382
  encrypt,
1383
+ errorIssue,
967
1384
  generateKeypair,
968
1385
  getAdornmentIcon,
969
1386
  getMinimizedConfig,
@@ -979,6 +1396,7 @@ export {
979
1396
  thumbnailImageExtensions,
980
1397
  transformDeployablePlugin,
981
1398
  triggerImageExtensions,
1399
+ validatePluginApp,
982
1400
  zipToUint8Array
983
1401
  };
984
1402
  //# sourceMappingURL=index.js.map