@fluid-app/fluid-cli-widget 0.1.26 → 0.1.28

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,1670 @@
1
+ import { createRequire } from "node:module";
2
+ import fs from "fs-extra";
3
+ import path from "node:path";
4
+ import { createHash } from "node:crypto";
5
+ import { execa } from "execa";
6
+ import { pathToFileURL } from "node:url";
7
+ import { failure, success } from "@fluid-app/fluid-cli";
8
+ //#region ../widget-tooling/src/errors.ts
9
+ var WidgetToolingError = class extends Error {
10
+ code;
11
+ details;
12
+ diagnostics;
13
+ constructor(code, message, options = {}) {
14
+ super(message, options);
15
+ this.name = "WidgetToolingError";
16
+ this.code = code;
17
+ this.details = options.details;
18
+ this.diagnostics = options.diagnostics;
19
+ }
20
+ };
21
+ const CONTENT_TYPES = {
22
+ ".css": "text/css",
23
+ ".js": "application/javascript",
24
+ ".json": "application/json",
25
+ ".map": "application/json"
26
+ };
27
+ const CSS_ARTIFACT_PATH_PATTERN = /^[A-Za-z0-9._~-]+\.css$/;
28
+ const JS_MAP_ARTIFACT_PATH_PATTERN = /^[A-Za-z0-9._~-]+\.js\.map$/;
29
+ async function computeArtifactMetadata(filePath, baseDir) {
30
+ const relativePath = toPosixPath$1(path.relative(baseDir, filePath));
31
+ assertAllowedArtifactPath(relativePath);
32
+ await assertRegularArtifactFile(filePath, relativePath);
33
+ const buffer = await fs.readFile(filePath);
34
+ return {
35
+ path: relativePath,
36
+ sha256: sha256(buffer),
37
+ bytes: buffer.byteLength,
38
+ contentType: getArtifactContentType(filePath)
39
+ };
40
+ }
41
+ async function collectWidgetPackageArtifacts(publishDir) {
42
+ const artifactPaths = await findArtifactPaths(publishDir);
43
+ const artifacts = await Promise.all(artifactPaths.map((artifactPath) => computeArtifactMetadata(artifactPath, publishDir)));
44
+ assertRequiredUploadArtifacts(artifacts);
45
+ return artifacts.sort((a, b) => a.path.localeCompare(b.path));
46
+ }
47
+ function createPublishManifestMetadata(options) {
48
+ return {
49
+ packageId: options.packageId,
50
+ version: options.version,
51
+ builderVersion: options.builderVersion ?? "0.1.0",
52
+ cliVersion: options.cliVersion,
53
+ runtimeVersion: options.runtimeVersion,
54
+ manifestHash: sha256(options.manifestContent),
55
+ artifacts: options.artifacts
56
+ };
57
+ }
58
+ function getArtifactContentType(filePath) {
59
+ if (path.basename(filePath).endsWith(".js.map")) return "application/json";
60
+ return CONTENT_TYPES[path.extname(filePath)] ?? "application/octet-stream";
61
+ }
62
+ function sha256(content) {
63
+ return createHash("sha256").update(content).digest("hex");
64
+ }
65
+ function isWidgetPackageCssArtifactPath(relativePath) {
66
+ return CSS_ARTIFACT_PATH_PATTERN.test(relativePath);
67
+ }
68
+ function isWidgetPackageJsMapArtifactPath(relativePath) {
69
+ return JS_MAP_ARTIFACT_PATH_PATTERN.test(relativePath);
70
+ }
71
+ async function assertRegularArtifactFile(filePath, relativePath) {
72
+ const stat = await fs.lstat(filePath);
73
+ if (stat.isSymbolicLink()) throw new Error(`Widget publish artifact ${JSON.stringify(relativePath)} must be a regular file and must not be a symbolic link.`);
74
+ if (!stat.isFile()) throw new Error(`Widget publish artifact ${JSON.stringify(relativePath)} must be a regular file.`);
75
+ }
76
+ async function findArtifactPaths(dir, baseDir = dir) {
77
+ if (!await fs.pathExists(dir)) return [];
78
+ const entries = await fs.readdir(dir, { withFileTypes: true });
79
+ return (await Promise.all(entries.map(async (entry) => {
80
+ const entryPath = path.join(dir, entry.name);
81
+ const relativePath = toPosixPath$1(path.relative(baseDir, entryPath));
82
+ if (entry.isSymbolicLink()) throw new Error(`Widget publish artifact ${JSON.stringify(relativePath)} must be a regular file and must not be a symbolic link.`);
83
+ if (isUploadArtifact(relativePath)) {
84
+ if (!entry.isFile()) throw new Error(`Widget publish artifact ${JSON.stringify(relativePath)} must be a regular file.`);
85
+ return [entryPath];
86
+ }
87
+ if (entry.isDirectory()) {
88
+ const nestedPaths = await findArtifactPaths(entryPath, baseDir);
89
+ if (nestedPaths.length === 0) throwUnsupportedArtifactPath(`${relativePath}/`);
90
+ return nestedPaths;
91
+ }
92
+ if (isAllowedGeneratedFile(relativePath)) return [];
93
+ throwUnsupportedArtifactPath(relativePath);
94
+ }))).flat();
95
+ }
96
+ function assertRequiredUploadArtifacts(artifacts) {
97
+ const artifactPaths = new Set(artifacts.map((artifact) => artifact.path));
98
+ for (const requiredPath of ["widget.js", "manifest.json"]) {
99
+ if (artifactPaths.has(requiredPath)) continue;
100
+ throw new Error(`Widget package publish output must include regular file ${JSON.stringify(requiredPath)}.`);
101
+ }
102
+ }
103
+ function assertAllowedArtifactPath(relativePath) {
104
+ if (isUploadArtifact(relativePath) || isAllowedGeneratedFile(relativePath)) return;
105
+ throwUnsupportedArtifactPath(relativePath);
106
+ }
107
+ function isUploadArtifact(relativePath) {
108
+ return relativePath === "widget.js" || relativePath === "manifest.json" || isWidgetPackageCssArtifactPath(relativePath) || isWidgetPackageJsMapArtifactPath(relativePath);
109
+ }
110
+ function isAllowedGeneratedFile(relativePath) {
111
+ return isFlatArtifactPath(relativePath) && relativePath === "publish-manifest.json";
112
+ }
113
+ function isFlatArtifactPath(relativePath) {
114
+ return relativePath.length > 0 && !relativePath.includes("/") && !relativePath.includes("\\");
115
+ }
116
+ function throwUnsupportedArtifactPath(relativePath) {
117
+ throw new Error(`Unsupported widget publish artifact ${JSON.stringify(relativePath)}. Allowed files are widget.js, manifest.json, top-level [A-Za-z0-9._~-]+.css, top-level [A-Za-z0-9._~-]+.js.map, and publish-manifest.json.`);
118
+ }
119
+ function toPosixPath$1(value) {
120
+ return value.split(path.sep).join("/");
121
+ }
122
+ //#endregion
123
+ //#region ../../portal/core/src/remote-dom-widget-package.ts
124
+ const UNSAFE_JSON_KEYS = new Set([
125
+ "__proto__",
126
+ "constructor",
127
+ "prototype"
128
+ ]);
129
+ function isJsonValue(value) {
130
+ return isJsonValueAtDepth(value, /* @__PURE__ */ new WeakSet(), 0);
131
+ }
132
+ function isJsonValueAtDepth(value, ancestors, depth) {
133
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
134
+ if (typeof value === "number") return Number.isFinite(value);
135
+ if (typeof value !== "object" || depth > 50 || ancestors.has(value)) return false;
136
+ ancestors.add(value);
137
+ const prototype = Object.getPrototypeOf(value);
138
+ const isJson = Array.isArray(value) ? value.every((item) => isJsonValueAtDepth(item, ancestors, depth + 1)) : (prototype === Object.prototype || prototype === null) && isRecord$5(value) && Object.entries(value).every(([key, item]) => !UNSAFE_JSON_KEYS.has(key) && isJsonValueAtDepth(item, ancestors, depth + 1));
139
+ ancestors.delete(value);
140
+ return isJson;
141
+ }
142
+ function isRecord$5(value) {
143
+ return typeof value === "object" && value !== null && !Array.isArray(value);
144
+ }
145
+ //#endregion
146
+ //#region ../widget-tooling/src/internal/widget-package-validation.ts
147
+ const URL_SAFE_SEGMENT_SOURCE = "[A-Za-z0-9][A-Za-z0-9_~-]*";
148
+ const URL_SAFE_WIDGET_NAME_PATTERN = new RegExp(`^${URL_SAFE_SEGMENT_SOURCE}$`);
149
+ const URL_SAFE_DOTTED_IDENTIFIER_PATTERN = new RegExp(`^${URL_SAFE_SEGMENT_SOURCE}(?:\\.${URL_SAFE_SEGMENT_SOURCE})*$`);
150
+ const SEMVER_CORE_PATTERN = "(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)";
151
+ const SEMVER_PRERELEASE_IDENTIFIER_PATTERN = "(?:0|[1-9]\\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)";
152
+ const SEMVER_URL_SAFE_PATTERN = new RegExp(`^${SEMVER_CORE_PATTERN}(?:-${SEMVER_PRERELEASE_IDENTIFIER_PATTERN}(?:\\.${SEMVER_PRERELEASE_IDENTIFIER_PATTERN})*)?$`);
153
+ const WIDGET_PACKAGE_MANIFEST_VERSION = 1;
154
+ const DEFAULT_WIDGET_ICON = "box";
155
+ const DEFAULT_WIDGET_CATEGORY = "components";
156
+ const DEFAULT_WIDGET_CONTAINER = "block";
157
+ const DEFAULT_MIN_SDK_VERSION = "0.0.0";
158
+ function validateSingleSourceWidgetPackage(sourcePackages, options = {}) {
159
+ if (sourcePackages.length === 0) return validationFailure({
160
+ code: "NO_SOURCE_PACKAGE",
161
+ message: "No defineWidgetPackage source package was found. Export widgetPackage, widgetPackages, or a default widget package from src/widgets.config.ts."
162
+ });
163
+ if (sourcePackages.length > 1) return validationFailure({
164
+ code: "MULTIPLE_SOURCE_PACKAGES",
165
+ message: "Publish/deploy supports exactly one defineWidgetPackage source package. Export one package or split packages into separate builds."
166
+ });
167
+ const sourcePackageValue = sourcePackages[0];
168
+ if (!sourcePackageValue) return validationFailure({
169
+ code: "NO_SOURCE_PACKAGE",
170
+ message: "No defineWidgetPackage source package was found."
171
+ });
172
+ const shapeErrors = validateSourcePackageShape(sourcePackageValue);
173
+ if (shapeErrors.length > 0) return {
174
+ success: false,
175
+ errors: shapeErrors
176
+ };
177
+ const sourcePackage = sourcePackageValue;
178
+ const errors = [];
179
+ const owner = options.owner ?? sourcePackage.packageType;
180
+ if (owner !== "company" && owner !== "droplet") errors.push({
181
+ code: "UNSUPPORTED_OWNER",
182
+ path: "packageType",
183
+ message: "Widget package publish/deploy currently supports company and droplet owners. Set packageType to \"company\" or \"droplet\"."
184
+ });
185
+ else if (options.owner !== void 0 && sourcePackage.packageType !== options.owner) errors.push({
186
+ code: "UNSUPPORTED_OWNER",
187
+ path: "packageType",
188
+ message: `Widget package metadata must set packageType to "${options.owner}" for this workflow.`
189
+ });
190
+ const resolvedOwner = owner === "droplet" ? "droplet" : "company";
191
+ if (resolvedOwner === "droplet" && !readNonEmptyTrimmedString(sourcePackage.packageStableId)) errors.push({
192
+ code: "INVALID_PACKAGE_KEY",
193
+ path: "packageStableId",
194
+ message: "Droplet widget packages must set packageStableId so published widget types remain stable across builds."
195
+ });
196
+ const packageKey = resolvePackageKey(sourcePackage, resolvedOwner);
197
+ const packageKeyPath = readNonEmptyTrimmedString(sourcePackage.packageStableId) ? "packageStableId" : "packageId";
198
+ if (!isUrlSafeDottedIdentifier(packageKey)) errors.push({
199
+ code: "INVALID_PACKAGE_KEY",
200
+ path: packageKeyPath,
201
+ message: "Widget package key must be URL-safe dot-separated text (letters, numbers, '.', '_', '~', and '-' only)."
202
+ });
203
+ if (!isUrlSafeSemver(sourcePackage.version)) errors.push({
204
+ code: "INVALID_VERSION",
205
+ path: "version",
206
+ message: "Widget package version must be a URL-safe SemVer version without build metadata (for example 1.2.3 or 1.2.3-beta.1)."
207
+ });
208
+ if (sourcePackage.widgets.length === 0) errors.push({
209
+ code: "NO_WIDGETS",
210
+ path: "widgets",
211
+ message: "Widget package must include at least one widget."
212
+ });
213
+ const packageId = `${resolvedOwner}.${packageKey}`;
214
+ const seenNames = /* @__PURE__ */ new Set();
215
+ const seenTypes = /* @__PURE__ */ new Set();
216
+ const widgets = [];
217
+ sourcePackage.widgets.forEach((widget, index) => {
218
+ const path = `widgets[${index}]`;
219
+ if (!isUrlSafeWidgetName(widget.name)) {
220
+ errors.push({
221
+ code: "INVALID_WIDGET_NAME",
222
+ path: `${path}.name`,
223
+ message: "Widget name must be URL-safe text (letters, numbers, '_', '~', and '-' only) and cannot be empty."
224
+ });
225
+ return;
226
+ }
227
+ if (seenNames.has(widget.name)) {
228
+ errors.push({
229
+ code: "DUPLICATE_WIDGET",
230
+ path: `${path}.name`,
231
+ message: `Duplicate widget name "${widget.name}" found in package ${packageKey}.`
232
+ });
233
+ return;
234
+ }
235
+ seenNames.add(widget.name);
236
+ const type = `${packageId}.${widget.name}`;
237
+ if (!isUrlSafeDottedIdentifier(type)) {
238
+ errors.push({
239
+ code: "INVALID_WIDGET_TYPE",
240
+ path: `${path}.type`,
241
+ message: `Generated widget type "${type}" is not URL-safe.`
242
+ });
243
+ return;
244
+ }
245
+ if (seenTypes.has(type)) {
246
+ errors.push({
247
+ code: "DUPLICATE_WIDGET",
248
+ path: `${path}.type`,
249
+ message: `Duplicate generated widget type "${type}" found.`
250
+ });
251
+ return;
252
+ }
253
+ seenTypes.add(type);
254
+ widgets.push(sourceWidgetToDescriptor(widget, type));
255
+ });
256
+ if (errors.length > 0) return {
257
+ success: false,
258
+ errors
259
+ };
260
+ return {
261
+ success: true,
262
+ value: {
263
+ sourcePackage,
264
+ packageKey,
265
+ packageId,
266
+ owner: resolvedOwner,
267
+ version: sourcePackage.version,
268
+ widgets
269
+ },
270
+ errors: []
271
+ };
272
+ }
273
+ function buildWidgetPackagePublishManifest(validated, options = {}) {
274
+ return {
275
+ manifestVersion: WIDGET_PACKAGE_MANIFEST_VERSION,
276
+ runtime: "remote-dom",
277
+ packageId: validated.packageId,
278
+ source: validated.owner,
279
+ version: validated.version,
280
+ workerEntryUrl: toWidgetPackageArtifactPath("widget.js"),
281
+ cssUrls: mergeCssUrls(validated.sourcePackage.cssUrls, options.cssUrls),
282
+ assetUrls: [],
283
+ widgets: validated.widgets.map(({ name: _name, ...widget }) => widget)
284
+ };
285
+ }
286
+ function sourceWidgetToDescriptor(widget, type) {
287
+ const displayName = widget.displayName ?? widget.name;
288
+ return {
289
+ type,
290
+ name: widget.name,
291
+ displayName,
292
+ description: widget.description ?? `Custom widget ${displayName}`,
293
+ icon: widget.icon ?? DEFAULT_WIDGET_ICON,
294
+ category: widget.category ?? DEFAULT_WIDGET_CATEGORY,
295
+ propertySchema: normalizePropertySchema(widget.propertySchema, type),
296
+ defaultProps: widget.defaultProps ?? {},
297
+ container: normalizeContainer(widget.container),
298
+ minSdkVersion: widget.minSdkVersion ?? DEFAULT_MIN_SDK_VERSION,
299
+ resizable: normalizeResizable(widget.resizable),
300
+ capabilities: normalizeCapabilities(widget.capabilities ?? [])
301
+ };
302
+ }
303
+ function normalizeCapabilities(capabilities) {
304
+ return capabilities.map((capability) => ({
305
+ name: capability.name,
306
+ version: capability.version ?? "1"
307
+ }));
308
+ }
309
+ function validationFailure(error) {
310
+ return {
311
+ success: false,
312
+ errors: [error]
313
+ };
314
+ }
315
+ function validateSourcePackageShape(value) {
316
+ if (!isRecord$4(value)) return [{
317
+ code: "INVALID_SOURCE_PACKAGE",
318
+ message: "Widget source package must be a JSON object."
319
+ }];
320
+ const errors = [];
321
+ requireStringField(value, "packageId", "packageId", errors);
322
+ requireStringField(value, "version", "version", errors);
323
+ validateOptionalStringField(value, "scope", "scope", errors);
324
+ validateOptionalStringField(value, "packageStableId", "packageStableId", errors);
325
+ validateOptionalStringField(value, "packageType", "packageType", errors);
326
+ if (Object.prototype.hasOwnProperty.call(value, "navigationOrigins")) errors.push({
327
+ code: "INVALID_SOURCE_PACKAGE",
328
+ path: "navigationOrigins",
329
+ message: "navigationOrigins has been removed. Declare the externalNavigation capability and call allowAnchorUrl(url) for each external link."
330
+ });
331
+ if (value.cssUrls !== void 0) if (!Array.isArray(value.cssUrls)) errors.push({
332
+ code: "INVALID_SOURCE_PACKAGE",
333
+ path: "cssUrls",
334
+ message: "Widget package cssUrls must be an array of strings when present."
335
+ });
336
+ else value.cssUrls.forEach((cssUrl, index) => {
337
+ validateCssUrl(cssUrl, `cssUrls[${index}]`, errors);
338
+ });
339
+ if (!Array.isArray(value.widgets)) {
340
+ errors.push({
341
+ code: "INVALID_SOURCE_PACKAGE",
342
+ path: "widgets",
343
+ message: "Widget package widgets must be an array."
344
+ });
345
+ return errors;
346
+ }
347
+ value.widgets.forEach((widget, index) => {
348
+ const widgetPath = `widgets[${index}]`;
349
+ if (!isRecord$4(widget)) {
350
+ errors.push({
351
+ code: "INVALID_WIDGET_METADATA",
352
+ path: widgetPath,
353
+ message: "Widget metadata must be a JSON object."
354
+ });
355
+ return;
356
+ }
357
+ requireStringField(widget, "name", `${widgetPath}.name`, errors, {
358
+ code: "INVALID_WIDGET_NAME",
359
+ message: "Widget name must be a string and cannot be empty."
360
+ });
361
+ validateOptionalStringField(widget, "displayName", `${widgetPath}.displayName`, errors, { requireNonEmpty: true });
362
+ validateOptionalStringField(widget, "description", `${widgetPath}.description`, errors, { requireNonEmpty: true });
363
+ validateOptionalStringField(widget, "icon", `${widgetPath}.icon`, errors, { requireNonEmpty: true });
364
+ validateOptionalStringField(widget, "category", `${widgetPath}.category`, errors, { requireNonEmpty: true });
365
+ validateOptionalStringField(widget, "minSdkVersion", `${widgetPath}.minSdkVersion`, errors, { requireNonEmpty: true });
366
+ validateOptionalJsonRecordField(widget, "propertySchema", `${widgetPath}.propertySchema`, errors);
367
+ validateOptionalJsonRecordField(widget, "defaultProps", `${widgetPath}.defaultProps`, errors);
368
+ validateOptionalCapabilities(widget, "capabilities", `${widgetPath}.capabilities`, errors);
369
+ });
370
+ return errors;
371
+ }
372
+ function requireStringField(record, key, path, errors, override) {
373
+ const value = record[key];
374
+ if (typeof value === "string" && value.trim().length > 0) return;
375
+ errors.push({
376
+ code: override?.code ?? "INVALID_SOURCE_PACKAGE",
377
+ path,
378
+ message: override?.message ?? `${path} must be a string and cannot be empty.`
379
+ });
380
+ }
381
+ function validateOptionalStringField(record, key, path, errors, options = {}) {
382
+ const value = record[key];
383
+ if (value === void 0) return;
384
+ if (typeof value === "string") {
385
+ if (!options.requireNonEmpty || value.trim().length > 0) return;
386
+ errors.push({
387
+ code: "INVALID_SOURCE_PACKAGE",
388
+ path,
389
+ message: `${path} must be a non-empty string when present.`
390
+ });
391
+ return;
392
+ }
393
+ errors.push({
394
+ code: "INVALID_SOURCE_PACKAGE",
395
+ path,
396
+ message: `${path} must be a string when present.`
397
+ });
398
+ }
399
+ function validateCssUrl(value, cssUrlPath, errors) {
400
+ if (typeof value !== "string" || value.trim().length === 0) {
401
+ errors.push({
402
+ code: "INVALID_SOURCE_PACKAGE",
403
+ path: cssUrlPath,
404
+ message: "Widget package cssUrls entries must be non-empty strings."
405
+ });
406
+ return;
407
+ }
408
+ if (!isWidgetPackageCssArtifactPath(value)) errors.push({
409
+ code: "INVALID_SOURCE_PACKAGE",
410
+ path: cssUrlPath,
411
+ message: "Widget package cssUrls entries must be top-level generated CSS artifact filenames matching [A-Za-z0-9._~-]+.css. Self-host external CSS or fonts via @font-face in widget CSS so the build can isolate them."
412
+ });
413
+ }
414
+ function validateOptionalJsonRecordField(record, key, path, errors) {
415
+ const value = record[key];
416
+ if (value === void 0) return;
417
+ if (isRecord$4(value) && isJsonValue(value)) return;
418
+ errors.push({
419
+ code: "INVALID_WIDGET_METADATA",
420
+ path,
421
+ message: `${path} must be a JSON object when present.`
422
+ });
423
+ }
424
+ function validateOptionalCapabilities(record, key, path, errors) {
425
+ const value = record[key];
426
+ if (value === void 0) return;
427
+ if (!Array.isArray(value)) {
428
+ errors.push({
429
+ code: "INVALID_WIDGET_METADATA",
430
+ path,
431
+ message: `${path} must be an array when present.`
432
+ });
433
+ return;
434
+ }
435
+ const names = /* @__PURE__ */ new Set();
436
+ value.forEach((candidate, index) => {
437
+ const capabilityPath = `${path}[${index}]`;
438
+ if (!isRecord$4(candidate)) {
439
+ errors.push({
440
+ code: "INVALID_WIDGET_METADATA",
441
+ path: capabilityPath,
442
+ message: `${capabilityPath} must be a JSON object.`
443
+ });
444
+ return;
445
+ }
446
+ const name = candidate.name;
447
+ if (!isCanonicalMetadataString(name)) {
448
+ errors.push({
449
+ code: "INVALID_WIDGET_METADATA",
450
+ path: `${capabilityPath}.name`,
451
+ message: `${capabilityPath}.name must be a non-empty string without surrounding whitespace or control characters.`
452
+ });
453
+ return;
454
+ }
455
+ const version = candidate.version;
456
+ if (version !== void 0 && !isCanonicalMetadataString(version)) errors.push({
457
+ code: "INVALID_WIDGET_METADATA",
458
+ path: `${capabilityPath}.version`,
459
+ message: `${capabilityPath}.version must be a non-empty string without surrounding whitespace or control characters when present.`
460
+ });
461
+ if (names.has(name)) {
462
+ errors.push({
463
+ code: "INVALID_WIDGET_METADATA",
464
+ path: `${capabilityPath}.name`,
465
+ message: `${path} must not contain duplicate capability name ${JSON.stringify(name)}.`
466
+ });
467
+ return;
468
+ }
469
+ names.add(name);
470
+ });
471
+ }
472
+ function isCanonicalMetadataString(value) {
473
+ return typeof value === "string" && value.length > 0 && value === value.trim() && !/\p{Cc}/u.test(value);
474
+ }
475
+ function mergeCssUrls(sourceCssUrls, generatedCssUrls) {
476
+ const generatedCssUrlSet = new Set(generatedCssUrls ?? []);
477
+ generatedCssUrls?.forEach((cssUrl, index) => {
478
+ validateGeneratedCssArtifactUrl(cssUrl, `generated cssUrls[${index}]`);
479
+ });
480
+ sourceCssUrls?.forEach((cssUrl, index) => {
481
+ validateGeneratedCssArtifactUrl(cssUrl, `cssUrls[${index}]`);
482
+ if (generatedCssUrlSet.has(cssUrl)) return;
483
+ throw new Error(`cssUrls[${index}] URL ${JSON.stringify(cssUrl)} must match a generated top-level CSS artifact. Import the CSS from the widget package bundle or remove it from cssUrls.`);
484
+ });
485
+ return Array.from(new Set([...sourceCssUrls ?? [], ...generatedCssUrls ?? []])).map(toWidgetPackageArtifactPath);
486
+ }
487
+ function toWidgetPackageArtifactPath(value) {
488
+ return value;
489
+ }
490
+ function validateGeneratedCssArtifactUrl(value, cssUrlPath) {
491
+ if (value.trim().length === 0) throw new Error(`${cssUrlPath} must be a non-empty string.`);
492
+ if (!isWidgetPackageCssArtifactPath(value)) throw new Error(`${cssUrlPath} must be a top-level generated CSS artifact filename matching [A-Za-z0-9._~-]+.css. Self-host external CSS or fonts via @font-face in widget CSS so the build can isolate them.`);
493
+ }
494
+ function normalizePropertySchema(value, widgetType) {
495
+ return {
496
+ ...value ?? {},
497
+ widgetType
498
+ };
499
+ }
500
+ function normalizeContainer(value) {
501
+ if (value === "inline" || value === "block" || value === "card" || value === "fullscreen") return value;
502
+ return DEFAULT_WIDGET_CONTAINER;
503
+ }
504
+ function normalizeResizable(value) {
505
+ if (typeof value === "boolean") return value;
506
+ if (value === "horizontal" || value === "vertical" || value === "both") return value;
507
+ if (!isRecord$4(value)) return false;
508
+ const horizontal = value.horizontal === true;
509
+ const vertical = value.vertical === true;
510
+ if (horizontal && vertical) return "both";
511
+ if (horizontal) return "horizontal";
512
+ if (vertical) return "vertical";
513
+ return false;
514
+ }
515
+ function resolvePackageKey(sourcePackage, owner) {
516
+ const packageStableId = readNonEmptyTrimmedString(sourcePackage.packageStableId);
517
+ if (packageStableId) return prefixPackageStableIdWithNonOwnerScope(packageStableId, sourcePackage.scope);
518
+ return stripOwnerScopeFromPackageId(sourcePackage.packageId, sourcePackage.scope, owner);
519
+ }
520
+ function prefixPackageStableIdWithNonOwnerScope(packageStableId, sourceScope) {
521
+ const sourceScopeText = readNonEmptyTrimmedString(sourceScope);
522
+ if (!sourceScopeText || isWidgetPackageOwnerKind(sourceScopeText)) return packageStableId;
523
+ const sourceScopePrefix = `${sourceScopeText}.`;
524
+ if (packageStableId.startsWith(sourceScopePrefix)) return packageStableId;
525
+ return `${sourceScopeText}.${packageStableId}`;
526
+ }
527
+ function stripOwnerScopeFromPackageId(packageId, sourceScope, owner) {
528
+ const ownerPrefix = `${owner}.`;
529
+ if (packageId.startsWith(ownerPrefix) && packageId.length > ownerPrefix.length) return packageId.slice(ownerPrefix.length);
530
+ const sourceScopeText = readNonEmptyTrimmedString(sourceScope);
531
+ if (isWidgetPackageOwnerKind(sourceScopeText) && packageId.startsWith(`${sourceScopeText}.`) && packageId.length > sourceScopeText.length + 1) return packageId.slice(sourceScopeText.length + 1);
532
+ return packageId;
533
+ }
534
+ function readNonEmptyTrimmedString(value) {
535
+ const trimmed = value?.trim();
536
+ return trimmed ? trimmed : void 0;
537
+ }
538
+ function isWidgetPackageOwnerKind(value) {
539
+ return value === "company" || value === "droplet";
540
+ }
541
+ function isUrlSafeDottedIdentifier(value) {
542
+ return URL_SAFE_DOTTED_IDENTIFIER_PATTERN.test(value);
543
+ }
544
+ function isUrlSafeWidgetName(value) {
545
+ return URL_SAFE_WIDGET_NAME_PATTERN.test(value);
546
+ }
547
+ function isUrlSafeSemver(value) {
548
+ return SEMVER_URL_SAFE_PATTERN.test(value);
549
+ }
550
+ function isRecord$4(value) {
551
+ return typeof value === "object" && value !== null && !Array.isArray(value);
552
+ }
553
+ //#endregion
554
+ //#region ../widget-tooling/src/development-descriptor.ts
555
+ function createWidgetPackageDevelopmentDescriptor(options) {
556
+ const validation = validateSingleSourceWidgetPackage(readSourcePackages(options.sourceModule), { owner: options.owner });
557
+ if (!validation.success || !validation.value) throw new WidgetToolingError("VALIDATION_FAILED", validation.errors.map((error) => error.message).join("\n") || "Widget package validation failed.", { diagnostics: validation.errors });
558
+ return validatedPackageToDescriptor(validation.value, options);
559
+ }
560
+ function validatedPackageToDescriptor(validated, options) {
561
+ const cssUrls = validated.sourcePackage.cssUrls ?? [];
562
+ const workerEntryPath = options.workerEntryPath ?? "/__runtime-entry__";
563
+ const defaultCssPath = options.defaultCssPath ?? "/styles.css";
564
+ return {
565
+ manifestVersion: 1,
566
+ runtime: "remote-dom",
567
+ packageId: validated.packageId,
568
+ source: validated.owner,
569
+ version: validated.version,
570
+ workerEntryUrl: new URL(workerEntryPath, options.origin).href,
571
+ cssUrls: cssUrls.length > 0 ? cssUrls.map((url) => toDevelopmentCssUrl(url, options.origin)) : [toDevelopmentCssUrl(defaultCssPath, options.origin)],
572
+ assetUrls: [],
573
+ widgets: validated.widgets.map(({ name: _name, ...widget }) => widget)
574
+ };
575
+ }
576
+ function readSourcePackages(sourceModule) {
577
+ const packages = sourceModule["widgetPackages"];
578
+ if (Array.isArray(packages) && packages.length > 0) return packages;
579
+ const widgetPackage = sourceModule["widgetPackage"];
580
+ if (widgetPackage !== void 0) return [widgetPackage];
581
+ const defaultPackage = sourceModule["default"];
582
+ return defaultPackage === void 0 ? [] : [defaultPackage];
583
+ }
584
+ function toDevelopmentCssUrl(url, origin) {
585
+ const { href } = new URL(url, origin);
586
+ return href.includes("?") ? `${href}&direct` : `${href}?direct`;
587
+ }
588
+ //#endregion
589
+ //#region ../widget-tooling/src/internal/widget-package-config.ts
590
+ const WIDGET_CONFIG_PATH = "src/widgets.config.ts";
591
+ const SOURCE_PACKAGE_EXTRACT_FILENAME = "extract-widget-packages.ts";
592
+ const SOURCE_PACKAGE_OUTPUT_FILENAME = "source-widget-packages.json";
593
+ const SOURCE_PACKAGE_OUTPUT_SENTINEL = "fluid-widget-source-packages:v1";
594
+ const TSX_CLI_PATH = createRequire(import.meta.url).resolve("tsx/cli");
595
+ async function resolvePortalWidgetSourceConfig(projectDir) {
596
+ const candidate = path.join(projectDir, WIDGET_CONFIG_PATH);
597
+ return await fs.pathExists(candidate) ? {
598
+ path: candidate,
599
+ relativePath: WIDGET_CONFIG_PATH
600
+ } : void 0;
601
+ }
602
+ async function loadSourceWidgetPackages(projectDir) {
603
+ const config = await resolvePortalWidgetSourceConfig(projectDir);
604
+ if (!config) return success([]);
605
+ let tempDir;
606
+ try {
607
+ tempDir = await createTempDirectory(projectDir);
608
+ const extractFile = path.join(tempDir, SOURCE_PACKAGE_EXTRACT_FILENAME);
609
+ const outputFile = path.join(tempDir, SOURCE_PACKAGE_OUTPUT_FILENAME);
610
+ await fs.writeFile(extractFile, createSourcePackageExtractorScript({
611
+ projectDir,
612
+ configPath: config.path
613
+ }), {
614
+ encoding: "utf-8",
615
+ flag: "wx"
616
+ });
617
+ await execa(process.execPath, [
618
+ TSX_CLI_PATH,
619
+ extractFile,
620
+ outputFile
621
+ ], {
622
+ cwd: projectDir,
623
+ stdio: "pipe",
624
+ env: {
625
+ ...process.env,
626
+ NODE_ENV: "production"
627
+ }
628
+ });
629
+ const outputResult = await readSourcePackageExtractorOutput(outputFile);
630
+ if (!outputResult.success) return outputResult;
631
+ const parsed = outputResult.value;
632
+ if (parsed.length === 0) return success([]);
633
+ return success(parsed);
634
+ } catch (err) {
635
+ const error = err;
636
+ return failure({
637
+ code: "CONFIG_LOAD_FAILED",
638
+ message: `Failed to load widget packages from ${config.relativePath}`,
639
+ details: error.stderr ?? error.message ?? String(err)
640
+ });
641
+ } finally {
642
+ if (tempDir) await fs.remove(tempDir).catch(() => {});
643
+ }
644
+ }
645
+ async function createTempDirectory(projectDir) {
646
+ const projectTmpDir = path.join(projectDir, ".fluid", "tmp");
647
+ try {
648
+ await fs.ensureDir(projectTmpDir);
649
+ return await fs.mkdtemp(path.join(projectTmpDir, "widget-packages-"));
650
+ } catch (err) {
651
+ const message = err instanceof Error ? err.message : String(err);
652
+ throw new Error(`Unable to create project-local temporary directory at ${projectTmpDir}: ${message}`);
653
+ }
654
+ }
655
+ async function readSourcePackageExtractorOutput(outputFile) {
656
+ let output;
657
+ try {
658
+ output = await fs.readFile(outputFile, "utf-8");
659
+ } catch (err) {
660
+ return failure({
661
+ code: "CONFIG_LOAD_FAILED",
662
+ message: "Widget package extractor did not write an output file",
663
+ details: err instanceof Error ? err.message : String(err)
664
+ });
665
+ }
666
+ let parsed;
667
+ try {
668
+ parsed = JSON.parse(output);
669
+ } catch {
670
+ return failure({
671
+ code: "INVALID_FORMAT",
672
+ message: "Failed to parse widget package output file as JSON",
673
+ details: `Output was: ${output.slice(0, 200)}`
674
+ });
675
+ }
676
+ if (!isRecord$3(parsed) || parsed.sentinel !== SOURCE_PACKAGE_OUTPUT_SENTINEL) return failure({
677
+ code: "INVALID_FORMAT",
678
+ message: "Widget package extractor output file had an invalid sentinel"
679
+ });
680
+ if (!Array.isArray(parsed.data)) return failure({
681
+ code: "INVALID_FORMAT",
682
+ message: "Extracted widget package output is not an array",
683
+ details: `Expected an array, got: ${typeof parsed.data}`
684
+ });
685
+ return success(parsed.data);
686
+ }
687
+ function createSourcePackageExtractorScript(options) {
688
+ return `
689
+ import fs from "node:fs/promises";
690
+ import path from "node:path";
691
+ import { createServer, normalizePath } from "vite";
692
+
693
+ const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
694
+ const OUTPUT_SENTINEL = ${JSON.stringify(SOURCE_PACKAGE_OUTPUT_SENTINEL)};
695
+ const projectRoot = ${JSON.stringify(options.projectDir)};
696
+ const widgetConfigPath = ${JSON.stringify(options.configPath)};
697
+ const outputPath = process.argv[2];
698
+ if (!outputPath) throw new Error("Missing widget package extractor output path.");
699
+
700
+ function toViteModuleId(modulePath) {
701
+ const relativePath = path.relative(projectRoot, modulePath);
702
+ if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
703
+ return "/" + normalizePath(relativePath);
704
+ }
705
+ return normalizePath(modulePath);
706
+ }
707
+
708
+ const server = await createServer({
709
+ root: projectRoot,
710
+ mode: "production",
711
+ server: { middlewareMode: true },
712
+ appType: "custom",
713
+ logLevel: "error",
714
+ clearScreen: false,
715
+ resolve: {
716
+ conditions: ["module", "browser", "development|production"],
717
+ },
718
+ ssr: {
719
+ noExternal: ["@fluid-app/portal-sdk"],
720
+ resolve: {
721
+ conditions: ["module", "node", "development|production"],
722
+ },
723
+ },
724
+ });
725
+
726
+ try {
727
+ const widgetConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
728
+
729
+ function isSourceWidgetPackage(value) {
730
+ return Boolean(value && typeof value === "object" && value[SOURCE_PACKAGE_MARKER] === true);
731
+ }
732
+
733
+ function collectSourceWidgetPackages(mod) {
734
+ const candidates = [
735
+ mod.widgetPackage,
736
+ ...(Array.isArray(mod.widgetPackages) ? mod.widgetPackages : []),
737
+ mod.default,
738
+ ];
739
+ const byPackageId = new Map();
740
+ for (const candidate of candidates) {
741
+ if (!isSourceWidgetPackage(candidate)) continue;
742
+ if (!byPackageId.has(candidate.packageId)) byPackageId.set(candidate.packageId, candidate);
743
+ }
744
+ return Array.from(byPackageId.values());
745
+ }
746
+
747
+ function serializeWidget(widget) {
748
+ if (!widget || typeof widget !== "object" || Array.isArray(widget)) {
749
+ return widget;
750
+ }
751
+ const { component, ...metadata } = widget;
752
+ return metadata;
753
+ }
754
+
755
+ function serializePackage(sourcePackage) {
756
+ return {
757
+ manifestVersion: sourcePackage.manifestVersion,
758
+ scope: sourcePackage.scope,
759
+ packageStableId: sourcePackage.packageStableId,
760
+ packageId: sourcePackage.packageId,
761
+ packageType: sourcePackage.packageType,
762
+ version: sourcePackage.version,
763
+ cssUrls: sourcePackage.cssUrls,
764
+ ...(Object.prototype.hasOwnProperty.call(sourcePackage, "navigationOrigins")
765
+ ? { navigationOrigins: sourcePackage.navigationOrigins }
766
+ : {}),
767
+ widgets: Array.isArray(sourcePackage.widgets)
768
+ ? sourcePackage.widgets.map(serializeWidget)
769
+ : sourcePackage.widgets,
770
+ };
771
+ }
772
+
773
+ await fs.writeFile(
774
+ outputPath,
775
+ JSON.stringify({
776
+ sentinel: OUTPUT_SENTINEL,
777
+ data: collectSourceWidgetPackages(widgetConfig).map(serializePackage),
778
+ }),
779
+ "utf-8",
780
+ );
781
+ } finally {
782
+ await server.close();
783
+ }
784
+ `;
785
+ }
786
+ function isRecord$3(value) {
787
+ return typeof value === "object" && value !== null && !Array.isArray(value);
788
+ }
789
+ //#endregion
790
+ //#region ../widget-tooling/src/internal/widget-package-builder.ts
791
+ const DEFAULT_PUBLISH_DIR = ".fluid/widget-dist";
792
+ const DEFAULT_RUNTIME_VERSION = "1";
793
+ const TEMP_BUILD_DIR = ".fluid/widget-build";
794
+ const TEMP_CLI_DIR = ".fluid/tmp";
795
+ const PROJECT_VITE_CONFIG_CANDIDATES = [
796
+ "vite.config.ts",
797
+ "vite.config.mts",
798
+ "vite.config.js",
799
+ "vite.config.mjs"
800
+ ];
801
+ const BLOCKED_FLUID_PROJECT_PLUGIN_NAMES = ["fluid-portal-dev", "fluid-backend-dev"];
802
+ const require = createRequire(import.meta.url);
803
+ function validateSharedWidgetPackagePublishDir(projectDir, publishDir = DEFAULT_PUBLISH_DIR, options = {}) {
804
+ const label = options.optionName ?? "publishDir";
805
+ if (path.isAbsolute(publishDir)) throw new Error(`${label} must be relative to the project directory.`);
806
+ const normalizedPublishDir = normalizePublishDirForValidation(publishDir);
807
+ if (normalizedPublishDir === ".fluid" || !normalizedPublishDir.startsWith(".fluid/")) throw new Error(`${label} must be a relative path under .fluid/.`);
808
+ if (isPathWithinReservedDir(normalizedPublishDir, TEMP_BUILD_DIR)) throw new Error(`${label} must not be ${TEMP_BUILD_DIR} because it is reserved for temporary builder output.`);
809
+ if (isPathWithinReservedDir(normalizedPublishDir, TEMP_CLI_DIR)) throw new Error(`${label} must not be ${TEMP_CLI_DIR} because it is reserved for Fluid CLI temporary files.`);
810
+ const resolvedProjectDir = path.resolve(projectDir);
811
+ const resolvedPublishDir = path.resolve(resolvedProjectDir, publishDir);
812
+ const relative = path.relative(resolvedProjectDir, resolvedPublishDir);
813
+ if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`${label} must stay inside the project directory.`);
814
+ }
815
+ async function buildSharedWidgetPackage(options) {
816
+ const publishDirInput = options.publishDir ?? DEFAULT_PUBLISH_DIR;
817
+ try {
818
+ await validateWidgetPackageOutputPaths(options.projectDir, publishDirInput);
819
+ } catch (err) {
820
+ return failure({
821
+ code: "INVALID_PUBLISH_DIR",
822
+ message: "Unsafe widget package publish directory",
823
+ details: (err instanceof Error ? err : new Error(String(err))).message
824
+ });
825
+ }
826
+ const sourcePackageResult = await loadSourceWidgetPackages(options.projectDir);
827
+ if (!sourcePackageResult.success) return failure({
828
+ code: "CONFIG_LOAD_FAILED",
829
+ message: sourcePackageResult.error.message,
830
+ details: sourcePackageResult.error.details
831
+ });
832
+ const validation = validateSingleSourceWidgetPackage(sourcePackageResult.value, { owner: options.owner });
833
+ if (!validation.success || !validation.value) return failure({
834
+ code: "VALIDATION_FAILED",
835
+ message: validation.errors.map((error) => error.message).join("\n")
836
+ });
837
+ try {
838
+ await validateWidgetPackageOutputPaths(options.projectDir, publishDirInput);
839
+ const publishDir = path.resolve(options.projectDir, publishDirInput);
840
+ const widgetScriptPath = path.join(publishDir, "widget.js");
841
+ await fs.emptyDir(publishDir);
842
+ const tempDir = path.resolve(options.projectDir, TEMP_BUILD_DIR);
843
+ await fs.emptyDir(tempDir);
844
+ let buildFailure;
845
+ try {
846
+ await buildWidgetScriptBundle({
847
+ projectDir: options.projectDir,
848
+ tempDir,
849
+ publishDir,
850
+ validated: validation.value
851
+ });
852
+ } catch (error) {
853
+ buildFailure = { error };
854
+ }
855
+ try {
856
+ await fs.remove(tempDir);
857
+ } catch {
858
+ const cleanupFailure = /* @__PURE__ */ new Error("Failed to remove reserved temporary build directory.");
859
+ if (buildFailure) {
860
+ const primaryError = buildFailure.error instanceof Error ? buildFailure.error : new Error(String(buildFailure.error));
861
+ throw new AggregateError([primaryError, cleanupFailure], `${primaryError.message}\n${cleanupFailure.message}`);
862
+ }
863
+ throw cleanupFailure;
864
+ }
865
+ if (buildFailure) throw buildFailure.error;
866
+ const cssUrls = await collectCssUrls(publishDir);
867
+ const descriptor = buildWidgetPackagePublishManifest(validation.value, { cssUrls });
868
+ const manifestContent = stableStringify(descriptor);
869
+ const publishPackageId = descriptor.packageId;
870
+ const manifestPath = path.join(publishDir, "manifest.json");
871
+ const publishManifestPath = path.join(publishDir, "publish-manifest.json");
872
+ await fs.writeFile(manifestPath, manifestContent, "utf-8");
873
+ const artifacts = await collectWidgetPackageArtifacts(publishDir);
874
+ const publishManifest = createPublishManifestMetadata({
875
+ packageId: publishPackageId,
876
+ version: validation.value.version,
877
+ cliVersion: options.cliVersion,
878
+ runtimeVersion: DEFAULT_RUNTIME_VERSION,
879
+ manifestContent,
880
+ artifacts
881
+ });
882
+ await fs.writeFile(publishManifestPath, stableStringify(publishManifest), "utf-8");
883
+ return success({
884
+ publishDir,
885
+ packageId: validation.value.packageId,
886
+ version: validation.value.version,
887
+ manifestPath,
888
+ publishManifestPath,
889
+ widgetScriptPath
890
+ });
891
+ } catch (err) {
892
+ const error = err instanceof Error ? err : new Error(String(err));
893
+ const isPublishDirValidationError = /^(publishDir|reserved temporary build directory) /.test(error.message);
894
+ return failure({
895
+ code: isPublishDirValidationError ? "INVALID_PUBLISH_DIR" : "WRITE_FAILED",
896
+ message: isPublishDirValidationError ? "Unsafe widget package publish directory" : "Failed to build shared widget package artifacts",
897
+ details: error.message
898
+ });
899
+ }
900
+ }
901
+ function createWidgetPackageEntrySource(options) {
902
+ return `import { createElement } from "react";
903
+ import { startWidgetPackage } from "@fluid-app/portal-sdk/widgets/worker";
904
+ ${options.cssImportPath ? `import ${JSON.stringify(options.cssImportPath)};` : ""}
905
+
906
+ const widgetConfig = await import(${JSON.stringify(options.configImportPath)});
907
+ const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
908
+ const descriptors = ${JSON.stringify(options.validated.widgets, null, 2)};
909
+
910
+ function isSourceWidgetPackage(value) {
911
+ return Boolean(value && typeof value === "object" && value[SOURCE_PACKAGE_MARKER] === true);
912
+ }
913
+
914
+ function collectSourceWidgetPackages(mod) {
915
+ return [
916
+ mod.widgetPackage,
917
+ ...(Array.isArray(mod.widgetPackages) ? mod.widgetPackages : []),
918
+ mod.default,
919
+ ].filter(isSourceWidgetPackage);
920
+ }
921
+
922
+ const sourcePackage = collectSourceWidgetPackages(widgetConfig)[0];
923
+ if (!sourcePackage) {
924
+ throw new Error("No Fluid source widget package found while loading widget package worker bundle.");
925
+ }
926
+
927
+ const componentsByName = new Map(
928
+ sourcePackage.widgets.map((widget) => [widget.name, widget.component]),
929
+ );
930
+
931
+ const widgets = descriptors.map((descriptor) => {
932
+ const component = componentsByName.get(descriptor.name);
933
+ if (typeof component !== "function") {
934
+ throw new Error("Widget package is missing Remote DOM component for " + descriptor.name + ".");
935
+ }
936
+ return {
937
+ ...descriptor,
938
+ component: (props) => createElement(component, props),
939
+ };
940
+ });
941
+
942
+ startWidgetPackage({ widgets });
943
+ `;
944
+ }
945
+ async function buildWidgetScriptBundle(options) {
946
+ const sourceConfig = await resolvePortalWidgetSourceConfig(options.projectDir);
947
+ if (!sourceConfig) throw new Error("No portal widget source config found.");
948
+ const entryPath = path.join(options.tempDir, "widget-entry.ts");
949
+ const viteConfigPath = path.join(options.tempDir, "vite.config.mjs");
950
+ const rootStylesPath = path.join(options.projectDir, "styles.css");
951
+ await fs.writeFile(entryPath, createWidgetPackageEntrySource({
952
+ configImportPath: pathToFileURL(sourceConfig.path).href,
953
+ cssImportPath: await fs.pathExists(rootStylesPath) ? pathToFileURL(rootStylesPath).href : void 0,
954
+ validated: options.validated
955
+ }), "utf-8");
956
+ await fs.writeFile(viteConfigPath, createViteConfigSource({
957
+ entryPath,
958
+ publishDir: options.publishDir,
959
+ projectConfigPath: await resolveProjectViteConfigPath(options.projectDir)
960
+ }), "utf-8");
961
+ const viteCliPath = resolveViteCliPath(options.projectDir);
962
+ await execa(process.execPath, [
963
+ viteCliPath,
964
+ "build",
965
+ "--config",
966
+ viteConfigPath
967
+ ], {
968
+ cwd: options.projectDir,
969
+ stdio: "pipe",
970
+ env: {
971
+ ...process.env,
972
+ NODE_ENV: "production"
973
+ }
974
+ });
975
+ }
976
+ function resolveViteCliPath(projectDir) {
977
+ const projectViteCliPath = resolveViteCliPathFromRequire(createRequire(path.join(projectDir, "package.json")));
978
+ if (projectViteCliPath) return projectViteCliPath;
979
+ const cliViteCliPath = resolveViteCliPathFromRequire(require);
980
+ if (cliViteCliPath) return cliViteCliPath;
981
+ throw new Error("Unable to resolve Vite for the portal project. Install the project dependencies and ensure vite is available before building widget packages.");
982
+ }
983
+ async function resolveProjectViteConfigPath(projectDir) {
984
+ for (const fileName of PROJECT_VITE_CONFIG_CANDIDATES) {
985
+ const candidate = path.join(projectDir, fileName);
986
+ if (await fs.pathExists(candidate)) return candidate;
987
+ }
988
+ }
989
+ function createViteConfigSource(options) {
990
+ const widgetConfigSource = createWidgetViteConfigObjectSource({
991
+ ...options,
992
+ disableConfigFileLookup: !options.projectConfigPath
993
+ });
994
+ if (!options.projectConfigPath) return `import { defineConfig } from "vite";
995
+
996
+ export default defineConfig(${widgetConfigSource});
997
+ `;
998
+ return `import { defineConfig, loadConfigFromFile, mergeConfig } from "vite";
999
+
1000
+ const projectConfigPath = ${JSON.stringify(options.projectConfigPath)};
1001
+ const widgetConfig = ${widgetConfigSource};
1002
+ const fluidWidgetDefineInvariants = {
1003
+ "process.env.NODE_ENV": JSON.stringify("production"),
1004
+ };
1005
+ const blockedFluidPluginNames = new Set(${JSON.stringify(BLOCKED_FLUID_PROJECT_PLUGIN_NAMES)});
1006
+
1007
+ function isAllowedProjectPlugin(plugin) {
1008
+ return !plugin || typeof plugin !== "object" || !blockedFluidPluginNames.has(plugin.name);
1009
+ }
1010
+
1011
+ function sanitizeProjectPlugins(pluginOption) {
1012
+ if (!Array.isArray(pluginOption)) {
1013
+ return isAllowedProjectPlugin(pluginOption) ? pluginOption : [];
1014
+ }
1015
+
1016
+ return pluginOption
1017
+ .map((plugin) => Array.isArray(plugin) ? sanitizeProjectPlugins(plugin) : plugin)
1018
+ .filter(isAllowedProjectPlugin);
1019
+ }
1020
+
1021
+ function copyProjectConfigValue(source, target, key) {
1022
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1023
+ target[key] = source[key];
1024
+ }
1025
+ }
1026
+
1027
+ function sanitizeProjectConfig(config) {
1028
+ if (!config || typeof config !== "object") return {};
1029
+
1030
+ const sanitized = {};
1031
+ if (Object.prototype.hasOwnProperty.call(config, "plugins")) {
1032
+ sanitized.plugins = sanitizeProjectPlugins(config.plugins);
1033
+ }
1034
+ copyProjectConfigValue(config, sanitized, "resolve");
1035
+ copyProjectConfigValue(config, sanitized, "define");
1036
+ copyProjectConfigValue(config, sanitized, "css");
1037
+ copyProjectConfigValue(config, sanitized, "esbuild");
1038
+ copyProjectConfigValue(config, sanitized, "json");
1039
+ copyProjectConfigValue(config, sanitized, "assetsInclude");
1040
+ copyProjectConfigValue(config, sanitized, "envDir");
1041
+ copyProjectConfigValue(config, sanitized, "envPrefix");
1042
+ return sanitized;
1043
+ }
1044
+
1045
+ function copyConfigDefineValue(value) {
1046
+ return value && typeof value === "object" && !Array.isArray(value)
1047
+ ? { ...value }
1048
+ : {};
1049
+ }
1050
+
1051
+ function mergeFluidWidgetDefineInvariants(mergedConfig, safeProjectConfig, widgetConfig) {
1052
+ mergedConfig.define = {
1053
+ ...copyConfigDefineValue(safeProjectConfig.define),
1054
+ ...copyConfigDefineValue(mergedConfig.define),
1055
+ ...copyConfigDefineValue(widgetConfig.define),
1056
+ ...fluidWidgetDefineInvariants,
1057
+ };
1058
+ return mergedConfig;
1059
+ }
1060
+
1061
+ export default defineConfig(async (configEnv) => {
1062
+ const projectConfig = await loadConfigFromFile(configEnv, projectConfigPath);
1063
+ const safeProjectConfig = sanitizeProjectConfig(projectConfig?.config ?? {});
1064
+ return mergeFluidWidgetDefineInvariants(mergeConfig(safeProjectConfig, widgetConfig), safeProjectConfig, widgetConfig);
1065
+ });
1066
+ `;
1067
+ }
1068
+ function createWidgetViteConfigObjectSource(options) {
1069
+ const configFileSource = options.disableConfigFileLookup ? " configFile: false,\n" : "";
1070
+ return `(() => {
1071
+ const fluidWidgetDefineInvariants = {
1072
+ "process.env.NODE_ENV": JSON.stringify("production"),
1073
+ };
1074
+
1075
+ function createFluidWidgetBuildInvariants() {
1076
+ return {
1077
+ copyPublicDir: false,
1078
+ emptyOutDir: false,
1079
+ outDir: ${JSON.stringify(options.publishDir)},
1080
+ sourcemap: false,
1081
+ target: "esnext",
1082
+ cssCodeSplit: false,
1083
+ lib: {
1084
+ entry: ${JSON.stringify(options.entryPath)},
1085
+ formats: ["es"],
1086
+ fileName: () => "widget.js",
1087
+ cssFileName: "widget",
1088
+ },
1089
+ rollupOptions: {
1090
+ output: {
1091
+ inlineDynamicImports: true,
1092
+ hoistTransitiveImports: false,
1093
+ },
1094
+ },
1095
+ };
1096
+ }
1097
+
1098
+ function fluidWidgetBuildInvariantsPlugin() {
1099
+ return {
1100
+ name: "fluid-widget-build-invariants",
1101
+ enforce: "post",
1102
+ config() {
1103
+ return { build: createFluidWidgetBuildInvariants() };
1104
+ },
1105
+ configResolved(config) {
1106
+ config.define = {
1107
+ ...(config.define ?? {}),
1108
+ ...fluidWidgetDefineInvariants,
1109
+ };
1110
+ Object.assign(config.build, createFluidWidgetBuildInvariants());
1111
+ },
1112
+ };
1113
+ }
1114
+
1115
+ return {
1116
+ ${configFileSource} publicDir: false,
1117
+ define: fluidWidgetDefineInvariants,
1118
+ resolve: {
1119
+ conditions: [
1120
+ "module",
1121
+ "browser",
1122
+ "development|production",
1123
+ ],
1124
+ },
1125
+ plugins: [fluidWidgetBuildInvariantsPlugin()],
1126
+ build: createFluidWidgetBuildInvariants(),
1127
+ };
1128
+ })()`;
1129
+ }
1130
+ async function validateWidgetPackageOutputPaths(projectDir, publishDir) {
1131
+ validateSharedWidgetPackagePublishDir(projectDir, publishDir);
1132
+ await rejectSymlinkedPathSegments({
1133
+ projectDir,
1134
+ relativePath: publishDir,
1135
+ label: "publishDir"
1136
+ });
1137
+ await rejectSymlinkedPathSegments({
1138
+ projectDir,
1139
+ relativePath: TEMP_BUILD_DIR,
1140
+ label: "reserved temporary build directory"
1141
+ });
1142
+ }
1143
+ async function collectCssUrls(publishDir) {
1144
+ return (await fs.readdir(publishDir, { withFileTypes: true })).filter((entry) => entry.isFile() && isWidgetPackageCssArtifactPath(entry.name)).map((entry) => entry.name).sort();
1145
+ }
1146
+ async function rejectSymlinkedPathSegments(options) {
1147
+ const resolvedProjectDir = path.resolve(options.projectDir);
1148
+ const resolvedTargetPath = path.resolve(resolvedProjectDir, options.relativePath);
1149
+ const segments = path.relative(resolvedProjectDir, resolvedTargetPath).split(path.sep).filter(Boolean);
1150
+ let currentPath = resolvedProjectDir;
1151
+ for (const segment of segments) {
1152
+ currentPath = path.join(currentPath, segment);
1153
+ let stat;
1154
+ try {
1155
+ stat = await fs.lstat(currentPath);
1156
+ } catch (err) {
1157
+ if (isNodeError(err) && err.code === "ENOENT") return;
1158
+ throw err;
1159
+ }
1160
+ if (stat.isSymbolicLink()) throw new Error(`${options.label} must not include symbolic links.`);
1161
+ }
1162
+ }
1163
+ function normalizePublishDirForValidation(value) {
1164
+ return toPosixPath(path.normalize(value)).replace(/\/+$/, "") || ".";
1165
+ }
1166
+ function isPathWithinReservedDir(value, reservedDir) {
1167
+ return value === reservedDir || value.startsWith(`${reservedDir}/`);
1168
+ }
1169
+ function toPosixPath(value) {
1170
+ return value.split(path.sep).join("/");
1171
+ }
1172
+ function stableStringify(value) {
1173
+ return `${JSON.stringify(value, null, 2)}\n`;
1174
+ }
1175
+ function resolveViteCliPathFromRequire(moduleRequire) {
1176
+ try {
1177
+ const packageJsonPath = moduleRequire.resolve("vite/package.json");
1178
+ const binPath = getViteBinPath(moduleRequire(packageJsonPath).bin);
1179
+ if (!binPath) return void 0;
1180
+ return path.resolve(path.dirname(packageJsonPath), binPath);
1181
+ } catch (err) {
1182
+ if (isModuleResolutionError(err)) return void 0;
1183
+ throw err;
1184
+ }
1185
+ }
1186
+ function getViteBinPath(bin) {
1187
+ if (typeof bin === "string") return bin;
1188
+ if (!isRecord$2(bin)) return void 0;
1189
+ const viteBin = bin.vite;
1190
+ return typeof viteBin === "string" ? viteBin : void 0;
1191
+ }
1192
+ function isRecord$2(value) {
1193
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1194
+ }
1195
+ function isNodeError(value) {
1196
+ return typeof value === "object" && value !== null && "code" in value && typeof value.code === "string";
1197
+ }
1198
+ function isModuleResolutionError(value) {
1199
+ return isNodeError(value) && (value.code === "MODULE_NOT_FOUND" || value.code === "ERR_MODULE_NOT_FOUND");
1200
+ }
1201
+ //#endregion
1202
+ //#region ../widget-tooling/src/internal/widget-package-publish-request.ts
1203
+ async function createWidgetPackagePublishRequest(options) {
1204
+ const manifestContent = await fs.readFile(options.manifestPath);
1205
+ const manifest = parseJsonRecord(manifestContent.toString("utf-8"), options.manifestPath);
1206
+ const publishManifest = await readPublishManifest(options.publishManifestPath);
1207
+ assertManifestIdentityMatches(manifest, publishManifest, options.manifestPath);
1208
+ assertMetadataMatches({
1209
+ label: "manifestHash",
1210
+ path: "manifest.json",
1211
+ expected: publishManifest.manifestHash,
1212
+ actual: sha256(manifestContent)
1213
+ });
1214
+ const collectedArtifacts = await collectWidgetPackageArtifacts(options.publishDir);
1215
+ assertArtifactInventoryMatches(publishManifest.artifacts, collectedArtifacts);
1216
+ const artifacts = await readUploadArtifacts(options.publishDir, publishManifest.artifacts);
1217
+ return {
1218
+ version: publishManifest.version,
1219
+ packageKey: stripOwnerPrefix(publishManifest.packageId, options.owner),
1220
+ builderVersion: publishManifest.builderVersion,
1221
+ cliVersion: publishManifest.cliVersion,
1222
+ runtimeVersion: publishManifest.runtimeVersion,
1223
+ manifestHash: publishManifest.manifestHash,
1224
+ manifest,
1225
+ artifacts
1226
+ };
1227
+ }
1228
+ async function readJsonRecord(filePath) {
1229
+ return parseJsonRecord(await fs.readFile(filePath, "utf-8"), filePath);
1230
+ }
1231
+ function parseJsonRecord(content, filePath) {
1232
+ const value = JSON.parse(content);
1233
+ if (!isRecord$1(value) || Array.isArray(value)) throw new Error(`${path.basename(filePath)} must contain a JSON object.`);
1234
+ return value;
1235
+ }
1236
+ async function readPublishManifest(filePath) {
1237
+ const record = await readJsonRecord(filePath);
1238
+ const artifactsValue = record["artifacts"];
1239
+ if (!Array.isArray(artifactsValue)) throw new Error("publish-manifest.json must include an artifacts array.");
1240
+ return {
1241
+ packageId: readRequiredString(record, "packageId", filePath),
1242
+ version: readRequiredString(record, "version", filePath),
1243
+ builderVersion: readRequiredString(record, "builderVersion", filePath),
1244
+ cliVersion: readRequiredString(record, "cliVersion", filePath),
1245
+ runtimeVersion: readRequiredString(record, "runtimeVersion", filePath),
1246
+ manifestHash: readRequiredString(record, "manifestHash", filePath),
1247
+ artifacts: artifactsValue.map((artifact, index) => readArtifactMetadata(artifact, index))
1248
+ };
1249
+ }
1250
+ function readArtifactMetadata(value, index) {
1251
+ if (!isRecord$1(value) || Array.isArray(value)) throw new Error(`publish-manifest.json artifacts[${index}] must be a JSON object.`);
1252
+ return {
1253
+ path: readRequiredString(value, "path", "publish-manifest.json"),
1254
+ sha256: readRequiredString(value, "sha256", "publish-manifest.json"),
1255
+ bytes: readRequiredNumber(value, "bytes", "publish-manifest.json"),
1256
+ contentType: readRequiredString(value, "contentType", "publish-manifest.json")
1257
+ };
1258
+ }
1259
+ async function readUploadArtifacts(publishDir, artifacts) {
1260
+ const resolvedPublishDir = path.resolve(publishDir);
1261
+ return Promise.all(artifacts.map(async (artifact) => {
1262
+ const artifactPath = resolveArtifactPath(resolvedPublishDir, artifact.path);
1263
+ const actual = await computeArtifactMetadata(artifactPath, resolvedPublishDir);
1264
+ assertArtifactMetadataMatches(artifact, actual);
1265
+ return {
1266
+ ...actual,
1267
+ body: await fs.readFile(artifactPath)
1268
+ };
1269
+ }));
1270
+ }
1271
+ function assertManifestIdentityMatches(manifest, publishManifest, manifestPath) {
1272
+ assertMetadataMatches({
1273
+ label: "packageId",
1274
+ path: "manifest.json",
1275
+ expected: publishManifest.packageId,
1276
+ actual: readRequiredString(manifest, "packageId", manifestPath)
1277
+ });
1278
+ assertMetadataMatches({
1279
+ label: "version",
1280
+ path: "manifest.json",
1281
+ expected: publishManifest.version,
1282
+ actual: readRequiredString(manifest, "version", manifestPath)
1283
+ });
1284
+ }
1285
+ function assertArtifactInventoryMatches(expected, actual) {
1286
+ const expectedByPath = /* @__PURE__ */ new Map();
1287
+ for (const artifact of expected) {
1288
+ if (expectedByPath.has(artifact.path)) throw new Error(`publish-manifest.json artifacts include duplicate path ${JSON.stringify(artifact.path)}. Rebuild the widget package before publishing.`);
1289
+ expectedByPath.set(artifact.path, artifact);
1290
+ }
1291
+ const actualPaths = /* @__PURE__ */ new Set();
1292
+ for (const actualArtifact of actual) {
1293
+ actualPaths.add(actualArtifact.path);
1294
+ const expectedArtifact = expectedByPath.get(actualArtifact.path);
1295
+ if (!expectedArtifact) throw new Error(`publish-manifest.json artifacts omit ${JSON.stringify(actualArtifact.path)}. Rebuild the widget package before publishing.`);
1296
+ assertArtifactMetadataMatches(expectedArtifact, actualArtifact);
1297
+ }
1298
+ for (const expectedArtifact of expected) {
1299
+ if (actualPaths.has(expectedArtifact.path)) continue;
1300
+ throw new Error(`publish-manifest.json artifacts include ${JSON.stringify(expectedArtifact.path)}, but that file is not present on disk. Rebuild the widget package before publishing.`);
1301
+ }
1302
+ }
1303
+ function assertArtifactMetadataMatches(expected, actual) {
1304
+ assertMetadataMatches({
1305
+ label: "path",
1306
+ path: expected.path,
1307
+ expected: expected.path,
1308
+ actual: actual.path
1309
+ });
1310
+ assertMetadataMatches({
1311
+ label: "sha256",
1312
+ path: expected.path,
1313
+ expected: expected.sha256,
1314
+ actual: actual.sha256
1315
+ });
1316
+ assertMetadataMatches({
1317
+ label: "bytes",
1318
+ path: expected.path,
1319
+ expected: expected.bytes,
1320
+ actual: actual.bytes
1321
+ });
1322
+ assertMetadataMatches({
1323
+ label: "contentType",
1324
+ path: expected.path,
1325
+ expected: expected.contentType,
1326
+ actual: actual.contentType
1327
+ });
1328
+ }
1329
+ function assertMetadataMatches(options) {
1330
+ if (options.expected === options.actual) return;
1331
+ throw new Error(`publish-manifest.json ${options.label} for ${JSON.stringify(options.path)} does not match the file on disk. Recorded ${JSON.stringify(options.expected)}, computed ${JSON.stringify(options.actual)}. Rebuild the widget package before publishing.`);
1332
+ }
1333
+ function resolveArtifactPath(publishDir, artifactPath) {
1334
+ const resolved = path.resolve(publishDir, artifactPath);
1335
+ const relative = path.relative(publishDir, resolved);
1336
+ if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Artifact path ${JSON.stringify(artifactPath)} escapes the publish directory.`);
1337
+ return resolved;
1338
+ }
1339
+ function stripOwnerPrefix(packageId, owner) {
1340
+ const prefix = `${owner}.`;
1341
+ if (packageId.startsWith(prefix) && packageId.length > prefix.length) return packageId.slice(prefix.length);
1342
+ return packageId;
1343
+ }
1344
+ function readRequiredString(record, key, filePath) {
1345
+ const value = record[key];
1346
+ if (typeof value === "string" && value.trim().length > 0) return value;
1347
+ throw new Error(`${path.basename(filePath)} must include string ${key}.`);
1348
+ }
1349
+ function readRequiredNumber(record, key, filePath) {
1350
+ const value = record[key];
1351
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1352
+ throw new Error(`${path.basename(filePath)} must include number ${key}.`);
1353
+ }
1354
+ function isRecord$1(value) {
1355
+ return typeof value === "object" && value !== null;
1356
+ }
1357
+ //#endregion
1358
+ //#region ../../api-clients/fluidos/src/namespaces/fluid_os_v0.ts
1359
+ /**
1360
+ * Create a Fluid OS widget package version
1361
+ * Start a company-owned Fluid OS widget package publish flow and return signed upload URLs for each artifact.
1362
+ *
1363
+ * @param client - Fetch client instance
1364
+ * @param body - body
1365
+ */
1366
+ async function fluid_os_v0_create_widget_package_version(client, body) {
1367
+ return client.post(`/api/company/fluid_os/widget_package_versions`, body);
1368
+ }
1369
+ /**
1370
+ * Complete a Fluid OS widget package upload
1371
+ * Verify uploaded artifacts for a company-owned Fluid OS widget package version and approve the version when verification succeeds. Uploaded manifest.json is verified for matching packageId, matching version, and valid company-owned `company.<package_key>.` widget type prefixes.
1372
+ *
1373
+ * @param client - Fetch client instance
1374
+ * @param version - version
1375
+ * @param [body] - body
1376
+ */
1377
+ async function fluid_os_v0_complete_widget_package_version_upload(client, version, body) {
1378
+ return client.post(`/api/company/fluid_os/widget_package_versions/${version}/complete_upload`, body);
1379
+ }
1380
+ //#endregion
1381
+ //#region ../widget-tooling/src/internal/widget-package-upload.ts
1382
+ var WidgetPackageUploadError = class extends Error {
1383
+ code;
1384
+ constructor(code, message) {
1385
+ super(message);
1386
+ this.name = "WidgetPackageUploadError";
1387
+ this.code = code;
1388
+ }
1389
+ };
1390
+ function buildWidgetPackageArtifactPayload(artifact) {
1391
+ return {
1392
+ path: artifact.path,
1393
+ sha256: artifact.sha256,
1394
+ bytes: artifact.bytes,
1395
+ contentType: artifact.contentType
1396
+ };
1397
+ }
1398
+ function buildWidgetPackageUploadSessionPayload(request) {
1399
+ return {
1400
+ version: request.version,
1401
+ package_key: request.packageKey,
1402
+ builder_version: request.builderVersion,
1403
+ cli_version: request.cliVersion,
1404
+ runtime_version: request.runtimeVersion,
1405
+ manifest_hash: request.manifestHash,
1406
+ manifest: request.manifest,
1407
+ artifacts: request.artifacts.map(buildWidgetPackageArtifactPayload)
1408
+ };
1409
+ }
1410
+ function buildWidgetPackageCompleteUploadPayload(packageKey, artifacts) {
1411
+ return {
1412
+ package_key: packageKey,
1413
+ artifacts: artifacts.map(buildWidgetPackageArtifactPayload)
1414
+ };
1415
+ }
1416
+ function getWidgetPackageUploadSessionRoute(owner) {
1417
+ switch (owner.kind) {
1418
+ case "droplet": return `/api/droplets/${encodeURIComponent(owner.uuid)}/widget_package_versions`;
1419
+ case "company": return "/api/company/fluid_os/widget_package_versions";
1420
+ }
1421
+ }
1422
+ function getWidgetPackageCompleteUploadRoute(owner, version) {
1423
+ switch (owner.kind) {
1424
+ case "droplet": return `/api/droplets/${encodeURIComponent(owner.uuid)}/widget_package_versions/${encodeURIComponent(version)}/complete_upload`;
1425
+ case "company": return `/api/company/fluid_os/widget_package_versions/${encodeURIComponent(version)}/complete_upload`;
1426
+ }
1427
+ }
1428
+ async function requestWidgetPackageUploadSession(client, owner, request) {
1429
+ const payload = buildWidgetPackageUploadSessionPayload(request);
1430
+ try {
1431
+ return normalizeWidgetPackageUploadSession(owner.kind === "company" ? await fluid_os_v0_create_widget_package_version(client, payload) : await client.post(getWidgetPackageUploadSessionRoute(owner), payload), request.version, request.artifacts);
1432
+ } catch (err) {
1433
+ if (err instanceof WidgetPackageUploadError) throw err;
1434
+ throw new WidgetPackageUploadError("SESSION_REQUEST_FAILED", `Failed to create widget package upload session: ${formatUnknownError(err)}`);
1435
+ }
1436
+ }
1437
+ async function uploadWidgetPackageArtifacts(session, artifacts, fetchImpl = fetch) {
1438
+ const uploadsByPath = new Map(session.uploads.map((upload) => [upload.path, upload]));
1439
+ const results = [];
1440
+ for (const artifact of artifacts) {
1441
+ const upload = uploadsByPath.get(artifact.path);
1442
+ if (!upload) throw new WidgetPackageUploadError("MISSING_SIGNED_URL", `Upload session did not include a signed URL for artifact "${artifact.path}".`);
1443
+ if (upload.contentType !== artifact.contentType) throw new WidgetPackageUploadError("CONTENT_TYPE_MISMATCH", `Upload session expected artifact "${artifact.path}" to use content type "${upload.contentType}", but local artifact uses "${artifact.contentType}".`);
1444
+ let response;
1445
+ try {
1446
+ response = await fetchImpl(upload.url, {
1447
+ method: "PUT",
1448
+ headers: mergeUploadHeaders(upload.headers, upload.contentType),
1449
+ body: artifact.body
1450
+ });
1451
+ } catch (err) {
1452
+ throw new WidgetPackageUploadError("ARTIFACT_UPLOAD_FAILED", `Failed to upload artifact "${artifact.path}": ${formatUnknownError(err)}`);
1453
+ }
1454
+ if (!response.ok) {
1455
+ const body = await response.text().catch(() => "");
1456
+ const details = body.trim() ? `: ${body.trim().slice(0, 500)}` : "";
1457
+ throw new WidgetPackageUploadError("ARTIFACT_UPLOAD_FAILED", `Failed to upload artifact "${artifact.path}": PUT returned HTTP ${response.status}${details}`);
1458
+ }
1459
+ results.push({
1460
+ path: artifact.path,
1461
+ status: response.status,
1462
+ contentType: upload.contentType
1463
+ });
1464
+ }
1465
+ return results;
1466
+ }
1467
+ async function completeWidgetPackageUpload(client, owner, version, packageKey, artifacts) {
1468
+ const payload = buildWidgetPackageCompleteUploadPayload(packageKey, artifacts);
1469
+ try {
1470
+ return normalizeWidgetPackageCompleteResponse(owner.kind === "company" ? await fluid_os_v0_complete_widget_package_version_upload(client, version, payload) : await client.post(getWidgetPackageCompleteUploadRoute(owner, version), payload), version);
1471
+ } catch (err) {
1472
+ if (err instanceof WidgetPackageUploadError) throw err;
1473
+ throw new WidgetPackageUploadError("COMPLETE_UPLOAD_FAILED", `Failed to complete widget package upload: ${formatUnknownError(err)}`);
1474
+ }
1475
+ }
1476
+ async function publishWidgetPackageVersion(options) {
1477
+ const sessionPayload = buildWidgetPackageUploadSessionPayload(options.request);
1478
+ if (options.dryRun) return {
1479
+ dryRun: true,
1480
+ sessionPayload
1481
+ };
1482
+ if (!options.client) throw new Error("A FetchClient is required when dryRun is false.");
1483
+ const session = await requestWidgetPackageUploadSession(options.client, options.owner, options.request);
1484
+ return {
1485
+ dryRun: false,
1486
+ sessionPayload,
1487
+ session,
1488
+ uploadedArtifacts: await uploadWidgetPackageArtifacts(session, options.request.artifacts, options.fetchImpl),
1489
+ complete: await completeWidgetPackageUpload(options.client, options.owner, session.version, options.request.packageKey, options.request.artifacts)
1490
+ };
1491
+ }
1492
+ function normalizeWidgetPackageUploadSession(response, requestedVersion, requestedArtifacts) {
1493
+ const record = requireRecord(response, "INVALID_SESSION_RESPONSE", "Upload session response must be a JSON object.");
1494
+ requireFiniteNumberProperty(record, "version_id", "INVALID_SESSION_RESPONSE");
1495
+ requireStringProperty(record, "artifact_base_url", "INVALID_SESSION_RESPONSE");
1496
+ const uploadValues = record["uploads"];
1497
+ if (!Array.isArray(uploadValues)) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", "Upload session response must contain an \"uploads\" array.");
1498
+ const requestedArtifactsByPath = new Map(requestedArtifacts.map((artifact) => [artifact.path, artifact]));
1499
+ const seenUploadPaths = /* @__PURE__ */ new Set();
1500
+ const uploads = uploadValues.map((value, index) => {
1501
+ const upload = requireRecord(value, "INVALID_SESSION_RESPONSE", `Upload session uploads[${index}] must be a JSON object.`);
1502
+ const path = requireStringProperty(upload, "path", "INVALID_SESSION_RESPONSE");
1503
+ const url = requireStringProperty(upload, "upload_url", "INVALID_SESSION_RESPONSE");
1504
+ requireStringProperty(upload, "public_url", "INVALID_SESSION_RESPONSE");
1505
+ const requestedArtifact = requestedArtifactsByPath.get(path);
1506
+ if (!requestedArtifact) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response included unexpected artifact "${path}".`);
1507
+ if (seenUploadPaths.has(path)) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response included duplicate artifact "${path}".`);
1508
+ seenUploadPaths.add(path);
1509
+ return {
1510
+ path,
1511
+ url,
1512
+ contentType: requestedArtifact.contentType,
1513
+ headers: {}
1514
+ };
1515
+ });
1516
+ const missingUploadPaths = requestedArtifacts.map((artifact) => artifact.path).filter((artifactPath) => !seenUploadPaths.has(artifactPath));
1517
+ if (missingUploadPaths.length > 0) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response is missing signed URLs for artifact(s): ${missingUploadPaths.join(", ")}.`);
1518
+ return {
1519
+ version: requestedVersion,
1520
+ uploads,
1521
+ raw: response
1522
+ };
1523
+ }
1524
+ function normalizeWidgetPackageCompleteResponse(response, requestedVersion) {
1525
+ const record = requireRecord(response, "INVALID_COMPLETE_RESPONSE", "Complete upload response must be a JSON object.");
1526
+ const responseVersion = requireStringProperty(record, "version", "INVALID_COMPLETE_RESPONSE");
1527
+ if (responseVersion !== requestedVersion) throw new WidgetPackageUploadError("INVALID_COMPLETE_RESPONSE", `Complete upload response version "${responseVersion}" did not match requested version "${requestedVersion}".`);
1528
+ const packageKey = requireStringProperty(record, "packageId", "INVALID_COMPLETE_RESPONSE");
1529
+ const status = requireStringProperty(record, "status", "INVALID_COMPLETE_RESPONSE");
1530
+ requireStringProperty(record, "scriptUrl", "INVALID_COMPLETE_RESPONSE");
1531
+ requireStringProperty(record, "manifestUrl", "INVALID_COMPLETE_RESPONSE");
1532
+ requireStringArrayProperty(record, "cssUrls", "INVALID_COMPLETE_RESPONSE");
1533
+ return {
1534
+ version: responseVersion,
1535
+ status,
1536
+ packageKey,
1537
+ raw: response
1538
+ };
1539
+ }
1540
+ function normalizeHeaderName(name) {
1541
+ return name.toLowerCase();
1542
+ }
1543
+ function mergeUploadHeaders(headers, contentType) {
1544
+ const merged = {};
1545
+ for (const [key, value] of Object.entries(headers)) {
1546
+ if (normalizeHeaderName(key) === "content-type") continue;
1547
+ merged[key] = value;
1548
+ }
1549
+ merged["Content-Type"] = contentType;
1550
+ return merged;
1551
+ }
1552
+ function requireRecord(value, code, message) {
1553
+ if (!isRecord(value) || Array.isArray(value)) throw new WidgetPackageUploadError(code, message);
1554
+ return value;
1555
+ }
1556
+ function isRecord(value) {
1557
+ return typeof value === "object" && value !== null;
1558
+ }
1559
+ function requireStringProperty(record, key, code) {
1560
+ const value = record[key];
1561
+ if (typeof value === "string" && value.trim().length > 0) return value;
1562
+ throw new WidgetPackageUploadError(code, `Response field "${key}" must be a non-empty string.`);
1563
+ }
1564
+ function requireFiniteNumberProperty(record, key, code) {
1565
+ const value = record[key];
1566
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1567
+ throw new WidgetPackageUploadError(code, `Response field "${key}" must be a finite number.`);
1568
+ }
1569
+ function requireStringArrayProperty(record, key, code) {
1570
+ const value = record[key];
1571
+ if (Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.trim().length > 0)) return value;
1572
+ throw new WidgetPackageUploadError(code, `Response field "${key}" must be an array of non-empty strings.`);
1573
+ }
1574
+ function formatUnknownError(err) {
1575
+ const base = err instanceof Error ? err.message : String(err);
1576
+ if (isRecord(err) && "data" in err) return `${base} — ${safeStringify(err.data)}`;
1577
+ return base;
1578
+ }
1579
+ function safeStringify(value) {
1580
+ const seen = /* @__PURE__ */ new WeakSet();
1581
+ try {
1582
+ return JSON.stringify(value, (_key, item) => {
1583
+ if (typeof item === "bigint") return `${item}n`;
1584
+ if (typeof item !== "object" || item === null) return item;
1585
+ if (seen.has(item)) return "[Circular]";
1586
+ seen.add(item);
1587
+ return item;
1588
+ }) ?? String(value);
1589
+ } catch {
1590
+ return "[unserializable error data]";
1591
+ }
1592
+ }
1593
+ //#endregion
1594
+ //#region ../widget-tooling/src/workflows.ts
1595
+ async function loadAndValidateWidgetPackage(options) {
1596
+ const sourceConfig = await resolvePortalWidgetSourceConfig(options.projectDir);
1597
+ const loadResult = await loadSourceWidgetPackages(options.projectDir);
1598
+ if (!loadResult.success) throw new WidgetToolingError("CONFIG_LOAD_FAILED", loadResult.error.message, { details: loadResult.error.details });
1599
+ const validation = validateSingleSourceWidgetPackage(loadResult.value, { owner: options.owner });
1600
+ if (!validation.success || !validation.value) throw new WidgetToolingError("VALIDATION_FAILED", validation.errors.map((error) => error.message).join("\n") || "Widget package validation failed.", { diagnostics: validation.errors });
1601
+ return {
1602
+ projectDir: options.projectDir,
1603
+ sourceConfigPath: sourceConfig?.relativePath ?? "src/widgets.config.ts",
1604
+ validated: validation.value
1605
+ };
1606
+ }
1607
+ async function buildWidgetPackage(options) {
1608
+ const result = await buildSharedWidgetPackage({
1609
+ projectDir: options.projectDir,
1610
+ publishDir: options.outDir,
1611
+ owner: options.owner,
1612
+ cliVersion: options.cliVersion
1613
+ });
1614
+ if (result.success) return result.value;
1615
+ throw new WidgetToolingError(result.error.code === "CONFIG_LOAD_FAILED" ? "CONFIG_LOAD_FAILED" : result.error.code === "VALIDATION_FAILED" ? "VALIDATION_FAILED" : result.error.code === "INVALID_PUBLISH_DIR" ? "INVALID_OUTPUT_DIR" : "BUILD_FAILED", result.error.message, { details: result.error.details });
1616
+ }
1617
+ async function publishWidgetPackage(options) {
1618
+ if (options.owner !== options.uploadOwner.kind) throw new WidgetToolingError("OWNER_MISMATCH", `Widget package build owner "${options.owner}" must match upload owner kind "${options.uploadOwner.kind}".`);
1619
+ if (!options.dryRun && !options.client) throw new WidgetToolingError("CLIENT_REQUIRED", "A FetchClient is required when dryRun is false.");
1620
+ const build = await buildWidgetPackage(options);
1621
+ let request;
1622
+ try {
1623
+ request = await createWidgetPackagePublishRequest({
1624
+ publishDir: build.publishDir,
1625
+ manifestPath: build.manifestPath,
1626
+ publishManifestPath: build.publishManifestPath,
1627
+ owner: options.owner
1628
+ });
1629
+ } catch (error) {
1630
+ throw new WidgetToolingError("ARTIFACT_VERIFICATION_FAILED", error instanceof Error ? error.message : "Failed to verify widget package artifacts.", { cause: error });
1631
+ }
1632
+ try {
1633
+ const publish = options.dryRun ? await publishWidgetPackageVersion({
1634
+ owner: options.uploadOwner,
1635
+ request,
1636
+ dryRun: true,
1637
+ fetchImpl: options.fetchImpl
1638
+ }) : await publishWidgetPackageVersion({
1639
+ client: options.client,
1640
+ owner: options.uploadOwner,
1641
+ request,
1642
+ fetchImpl: options.fetchImpl
1643
+ });
1644
+ return {
1645
+ build,
1646
+ request,
1647
+ publish
1648
+ };
1649
+ } catch (error) {
1650
+ if (error instanceof WidgetToolingError) throw error;
1651
+ if (error instanceof WidgetPackageUploadError) throw new WidgetToolingError(toPublicUploadErrorCode(error.code), error.message, { cause: error });
1652
+ throw new WidgetToolingError("PUBLISH_FAILED", error instanceof Error ? error.message : "Failed to publish widget package.", { cause: error });
1653
+ }
1654
+ }
1655
+ function toPublicUploadErrorCode(code) {
1656
+ switch (code) {
1657
+ case "SESSION_REQUEST_FAILED":
1658
+ case "INVALID_SESSION_RESPONSE":
1659
+ case "MISSING_SIGNED_URL":
1660
+ case "CONTENT_TYPE_MISMATCH":
1661
+ case "ARTIFACT_UPLOAD_FAILED":
1662
+ case "COMPLETE_UPLOAD_FAILED":
1663
+ case "INVALID_COMPLETE_RESPONSE": return code;
1664
+ default: return "PUBLISH_FAILED";
1665
+ }
1666
+ }
1667
+ //#endregion
1668
+ export { WidgetToolingError as a, createWidgetPackageDevelopmentDescriptor as i, loadAndValidateWidgetPackage as n, publishWidgetPackage as r, buildWidgetPackage as t };
1669
+
1670
+ //# sourceMappingURL=src-CVadzcYM.mjs.map