@fluid-app/fluid-cli-portal 0.1.41 → 0.1.42
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/README.md +19 -3
- package/dist/index.d.mts +32 -412
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +723 -2270
- package/dist/index.mjs.map +1 -1
- package/dist/{pull-CG5dELOX.mjs → pull-Cl7omGLf.mjs} +10 -537
- package/dist/pull-Cl7omGLf.mjs.map +1 -0
- package/dist/vite-plugin.d.mts +2 -5659
- package/dist/vite-plugin.d.mts.map +1 -1
- package/dist/vite-plugin.mjs +1 -71
- package/dist/vite-plugin.mjs.map +1 -1
- package/package.json +5 -4
- package/templates/base/AGENTS.md +3 -2
- package/templates/base/skills/fluid-portal-authoring/SKILL.md +3 -2
- package/templates/base/src/main.tsx +2 -2
- package/templates/base/src/portal.config.ts +3 -5
- package/templates/base/src/widgets.config.ts +8 -0
- package/templates/starter/README.md.template +4 -4
- package/templates/starter/vite.config.ts +2 -13
- package/dist/pull-CG5dELOX.mjs.map +0 -1
- package/templates/base/preview.html +0 -20
- package/templates/base/src/preview-entry.tsx +0 -39
- package/templates/base/vercel.json +0 -23
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as writeMappings, S as updateMapping, _ as deriveSlug, a as buildThemeIdToSlugMap, b as resolveIdToSlug, c as transformNavigationItems, d as transformTheme, f as buildSnapshot, g as writeSnapshot, h as readSnapshot, i as buildNavigationIdToSlugMap, l as transformProfile, m as diffAgainstSnapshot, o as deriveScreenSlug, p as computeFileHash, r as buildIdToSlugMap, s as transformNavigation, t as pullCommand, u as transformScreen, v as readMappings, x as resolveSlugToId, y as removeMapping } from "./pull-Cl7omGLf.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import chalk from "chalk";
|
|
@@ -12,9 +12,10 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileS
|
|
|
12
12
|
import Handlebars from "handlebars";
|
|
13
13
|
import { execa } from "execa";
|
|
14
14
|
import os from "node:os";
|
|
15
|
-
import
|
|
16
|
-
import path$1 from "path";
|
|
15
|
+
import { createFetchClient, fluidOs } from "@fluid-app/fluidos-api-client";
|
|
17
16
|
import { createHash } from "node:crypto";
|
|
17
|
+
import fs from "fs-extra";
|
|
18
|
+
import { isJsonValue } from "@fluid-app/portal-core/remote-dom-widget-package";
|
|
18
19
|
//#region src/types.ts
|
|
19
20
|
/**
|
|
20
21
|
* Available project templates
|
|
@@ -1149,7 +1150,7 @@ async function autoPull(cwd) {
|
|
|
1149
1150
|
console.log(chalk.yellow("No portal/ directory found.") + " Attempting to pull content...");
|
|
1150
1151
|
console.log();
|
|
1151
1152
|
try {
|
|
1152
|
-
const { pullCommand } = await import("./pull-
|
|
1153
|
+
const { pullCommand } = await import("./pull-Cl7omGLf.mjs").then((n) => n.n);
|
|
1153
1154
|
await pullCommand.parseAsync([], { from: "user" });
|
|
1154
1155
|
return hasPortalContent(cwd);
|
|
1155
1156
|
} catch (err) {
|
|
@@ -1204,615 +1205,173 @@ const devCommand = new Command("dev").description("Start the development server
|
|
|
1204
1205
|
}
|
|
1205
1206
|
});
|
|
1206
1207
|
//#endregion
|
|
1207
|
-
//#region src/
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
"
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const SOURCE_PACKAGE_OUTPUT_FILENAME = "source-widget-packages.json";
|
|
1215
|
-
const SOURCE_PACKAGE_OUTPUT_SENTINEL = "fluid-widget-source-packages:v1";
|
|
1216
|
-
const FLUID_WIDGET_AUTHORING_NODE_CONDITIONS = ["--conditions=fluid-widget-authoring", "--conditions=production"];
|
|
1217
|
-
const TSX_CLI_PATH$1 = createRequire(import.meta.url).resolve("tsx/cli");
|
|
1218
|
-
function withFluidWidgetAuthoringNodeCondition(nodeOptions) {
|
|
1219
|
-
const existingOptions = nodeOptions?.trim();
|
|
1220
|
-
const options = existingOptions ? existingOptions.split(/\s+/) : [];
|
|
1221
|
-
const optionSet = new Set(options);
|
|
1222
|
-
for (const condition of FLUID_WIDGET_AUTHORING_NODE_CONDITIONS) if (!optionSet.has(condition)) options.push(condition);
|
|
1223
|
-
return options.join(" ");
|
|
1224
|
-
}
|
|
1225
|
-
async function resolvePortalWidgetSourceConfig(projectDir) {
|
|
1226
|
-
for (const relativePath of CONFIG_CANDIDATES) {
|
|
1227
|
-
const candidate = path.join(projectDir, relativePath);
|
|
1228
|
-
if (await fs.pathExists(candidate)) return {
|
|
1229
|
-
path: candidate,
|
|
1230
|
-
relativePath
|
|
1231
|
-
};
|
|
1208
|
+
//#region src/commands/build.ts
|
|
1209
|
+
const buildCommand = new Command("build").description("Build the application for production").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
|
|
1210
|
+
const cwd = process.cwd();
|
|
1211
|
+
if (!existsSync(join(cwd, "package.json"))) {
|
|
1212
|
+
console.error(chalk.red("Error: No package.json found in current directory"));
|
|
1213
|
+
console.error(chalk.yellow("Make sure you're in a Fluid project directory"));
|
|
1214
|
+
process.exit(1);
|
|
1232
1215
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1216
|
+
if (!existsSync(join(cwd, "vite.config.ts"))) {
|
|
1217
|
+
console.error(chalk.red("Error: No vite.config.ts found"));
|
|
1218
|
+
console.error(chalk.yellow("This command must be run from a Fluid project directory"));
|
|
1219
|
+
process.exit(1);
|
|
1220
|
+
}
|
|
1221
|
+
console.log();
|
|
1222
|
+
console.log(chalk.bold("Building for production..."));
|
|
1223
|
+
console.log();
|
|
1224
|
+
const spinner = ora("Building...").start();
|
|
1238
1225
|
try {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
await fs.writeFile(extractFile, createSourcePackageExtractorScript({
|
|
1243
|
-
projectDir,
|
|
1244
|
-
configPath: config.path
|
|
1245
|
-
}), {
|
|
1246
|
-
encoding: "utf-8",
|
|
1247
|
-
flag: "wx"
|
|
1248
|
-
});
|
|
1249
|
-
await execa(process.execPath, [
|
|
1250
|
-
TSX_CLI_PATH$1,
|
|
1251
|
-
extractFile,
|
|
1252
|
-
outputFile
|
|
1253
|
-
], {
|
|
1254
|
-
cwd: projectDir,
|
|
1255
|
-
stdio: "pipe",
|
|
1256
|
-
env: {
|
|
1257
|
-
...process.env,
|
|
1258
|
-
NODE_ENV: "production",
|
|
1259
|
-
NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
|
|
1260
|
-
}
|
|
1261
|
-
});
|
|
1262
|
-
const outputResult = await readSourcePackageExtractorOutput(outputFile);
|
|
1263
|
-
if (!outputResult.success) return outputResult;
|
|
1264
|
-
const parsed = outputResult.value;
|
|
1265
|
-
if (parsed.length === 0) return success([]);
|
|
1266
|
-
return success(parsed);
|
|
1267
|
-
} catch (err) {
|
|
1268
|
-
const error = err;
|
|
1269
|
-
return failure({
|
|
1270
|
-
code: "CONFIG_LOAD_FAILED",
|
|
1271
|
-
message: `Failed to load widget packages from ${config.relativePath}`,
|
|
1272
|
-
details: error.stderr ?? error.message ?? String(err)
|
|
1226
|
+
await execa("pnpm", ["run", "build"], {
|
|
1227
|
+
cwd,
|
|
1228
|
+
stdio: "pipe"
|
|
1273
1229
|
});
|
|
1274
|
-
|
|
1275
|
-
|
|
1230
|
+
spinner.succeed("Build completed");
|
|
1231
|
+
const outDir = options.outDir ?? "dist";
|
|
1232
|
+
console.log();
|
|
1233
|
+
console.log(`Output written to ${chalk.cyan(outDir)}/`);
|
|
1234
|
+
console.log();
|
|
1235
|
+
console.log("To preview the build locally:");
|
|
1236
|
+
console.log(chalk.cyan(" pnpm vite preview"));
|
|
1237
|
+
console.log();
|
|
1238
|
+
} catch (error) {
|
|
1239
|
+
spinner.fail("Build failed");
|
|
1240
|
+
const execaError = error;
|
|
1241
|
+
if (execaError.stderr) console.error(execaError.stderr);
|
|
1242
|
+
process.exit(1);
|
|
1276
1243
|
}
|
|
1244
|
+
});
|
|
1245
|
+
//#endregion
|
|
1246
|
+
//#region src/utils/push-validation.ts
|
|
1247
|
+
/**
|
|
1248
|
+
* Cross-reference validation and change categorization utilities for the push command.
|
|
1249
|
+
*
|
|
1250
|
+
* Extracted into a standalone utility so that pure logic can be tested
|
|
1251
|
+
* without pulling in CLI dependencies (ora, chalk, prompts, etc.).
|
|
1252
|
+
*/
|
|
1253
|
+
/**
|
|
1254
|
+
* Extract the slug from a file path (e.g., "screens/home.json" -> "home").
|
|
1255
|
+
*/
|
|
1256
|
+
function slugFromPath(filePath) {
|
|
1257
|
+
return basename(filePath, ".json");
|
|
1277
1258
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1259
|
+
/**
|
|
1260
|
+
* Extract the resource type directory from a file path (e.g., "screens/home.json" -> "screens").
|
|
1261
|
+
*/
|
|
1262
|
+
function resourceTypeFromPath(filePath) {
|
|
1263
|
+
const dir = filePath.split("/")[0];
|
|
1264
|
+
if (dir === "screens" || dir === "themes" || dir === "navigations" || dir === "profiles") return dir;
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Read and parse a JSON file from the portal directory.
|
|
1269
|
+
*/
|
|
1270
|
+
async function readPortalFile$1(portalDir, relativePath) {
|
|
1271
|
+
const content = await readFile(join(portalDir, relativePath), "utf-8");
|
|
1272
|
+
return JSON.parse(content);
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Categorize a snapshot diff into resource-type-specific change lists.
|
|
1276
|
+
*/
|
|
1277
|
+
function categorizeChanges(diff) {
|
|
1278
|
+
const result = {
|
|
1279
|
+
screens: {
|
|
1280
|
+
new: [],
|
|
1281
|
+
changed: [],
|
|
1282
|
+
deleted: []
|
|
1283
|
+
},
|
|
1284
|
+
themes: {
|
|
1285
|
+
new: [],
|
|
1286
|
+
changed: [],
|
|
1287
|
+
deleted: []
|
|
1288
|
+
},
|
|
1289
|
+
navigations: {
|
|
1290
|
+
new: [],
|
|
1291
|
+
changed: [],
|
|
1292
|
+
deleted: []
|
|
1293
|
+
},
|
|
1294
|
+
profiles: {
|
|
1295
|
+
new: [],
|
|
1296
|
+
changed: [],
|
|
1297
|
+
deleted: []
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
for (const file of diff.new) {
|
|
1301
|
+
const type = resourceTypeFromPath(file);
|
|
1302
|
+
if (type) result[type].new.push(file);
|
|
1286
1303
|
}
|
|
1304
|
+
for (const file of diff.changed) {
|
|
1305
|
+
const type = resourceTypeFromPath(file);
|
|
1306
|
+
if (type) result[type].changed.push(file);
|
|
1307
|
+
}
|
|
1308
|
+
for (const file of diff.deleted) {
|
|
1309
|
+
const type = resourceTypeFromPath(file);
|
|
1310
|
+
if (type) result[type].deleted.push(file);
|
|
1311
|
+
}
|
|
1312
|
+
return result;
|
|
1287
1313
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1314
|
+
/**
|
|
1315
|
+
* Validate that all cross-references between local portal files are valid.
|
|
1316
|
+
*
|
|
1317
|
+
* Checks:
|
|
1318
|
+
* - Navigation items' "screen" slugs reference existing screen files or mappings
|
|
1319
|
+
* - Profile "navigation" and "mobile_navigation" slugs reference existing nav files or mappings
|
|
1320
|
+
* - Profile "themes" slugs reference existing theme files or mappings
|
|
1321
|
+
*/
|
|
1322
|
+
async function validateCrossReferences(portalDir, mappings, changes) {
|
|
1323
|
+
const errors = [];
|
|
1324
|
+
const validScreenSlugs = buildValidSlugsSet(portalDir, "screens", mappings);
|
|
1325
|
+
const validNavSlugs = buildValidSlugsSet(portalDir, "navigations", mappings);
|
|
1326
|
+
const validThemeSlugs = buildValidSlugsSet(portalDir, "themes", mappings);
|
|
1327
|
+
for (const file of changes.screens.deleted) validScreenSlugs.delete(slugFromPath(file));
|
|
1328
|
+
for (const file of changes.navigations.deleted) validNavSlugs.delete(slugFromPath(file));
|
|
1329
|
+
for (const file of changes.themes.deleted) validThemeSlugs.delete(slugFromPath(file));
|
|
1330
|
+
const navFilesToCheck = [...changes.navigations.new, ...changes.navigations.changed];
|
|
1331
|
+
for (const file of navFilesToCheck) try {
|
|
1332
|
+
validateNavigationItems((await readPortalFile$1(portalDir, file)).navigation_items, file, validScreenSlugs, errors);
|
|
1333
|
+
} catch {
|
|
1334
|
+
errors.push({
|
|
1335
|
+
file,
|
|
1336
|
+
message: "Failed to read navigation file"
|
|
1297
1337
|
});
|
|
1298
1338
|
}
|
|
1299
|
-
|
|
1300
|
-
try {
|
|
1301
|
-
|
|
1339
|
+
const profileFilesToCheck = [...changes.profiles.new, ...changes.profiles.changed];
|
|
1340
|
+
for (const file of profileFilesToCheck) try {
|
|
1341
|
+
const profile = await readPortalFile$1(portalDir, file);
|
|
1342
|
+
if (profile.navigation && !validNavSlugs.has(profile.navigation)) errors.push({
|
|
1343
|
+
file,
|
|
1344
|
+
message: `References navigation "${profile.navigation}" which does not exist`
|
|
1345
|
+
});
|
|
1346
|
+
if (profile.mobile_navigation && !validNavSlugs.has(profile.mobile_navigation)) errors.push({
|
|
1347
|
+
file,
|
|
1348
|
+
message: `References mobile_navigation "${profile.mobile_navigation}" which does not exist`
|
|
1349
|
+
});
|
|
1350
|
+
for (const themeSlug of profile.themes) if (!validThemeSlugs.has(themeSlug)) errors.push({
|
|
1351
|
+
file,
|
|
1352
|
+
message: `References theme "${themeSlug}" which does not exist`
|
|
1353
|
+
});
|
|
1302
1354
|
} catch {
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
message: "Failed to
|
|
1306
|
-
details: `Output was: ${output.slice(0, 200)}`
|
|
1355
|
+
errors.push({
|
|
1356
|
+
file,
|
|
1357
|
+
message: "Failed to read profile file"
|
|
1307
1358
|
});
|
|
1308
1359
|
}
|
|
1309
|
-
|
|
1310
|
-
code: "INVALID_FORMAT",
|
|
1311
|
-
message: "Widget package extractor output file had an invalid sentinel"
|
|
1312
|
-
});
|
|
1313
|
-
if (!Array.isArray(parsed.data)) return failure({
|
|
1314
|
-
code: "INVALID_FORMAT",
|
|
1315
|
-
message: "Extracted widget package output is not an array",
|
|
1316
|
-
details: `Expected an array, got: ${typeof parsed.data}`
|
|
1317
|
-
});
|
|
1318
|
-
return success(parsed.data);
|
|
1360
|
+
return errors;
|
|
1319
1361
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
const
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
const
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
function toViteModuleId(modulePath) {
|
|
1334
|
-
const relativePath = path.relative(projectRoot, modulePath);
|
|
1335
|
-
if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
1336
|
-
return "/" + normalizePath(relativePath);
|
|
1337
|
-
}
|
|
1338
|
-
return normalizePath(modulePath);
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
const server = await createServer({
|
|
1342
|
-
root: projectRoot,
|
|
1343
|
-
mode: "production",
|
|
1344
|
-
server: { middlewareMode: true },
|
|
1345
|
-
appType: "custom",
|
|
1346
|
-
logLevel: "error",
|
|
1347
|
-
clearScreen: false,
|
|
1348
|
-
resolve: {
|
|
1349
|
-
conditions: ["fluid-widget-authoring", "module", "browser", "development|production"],
|
|
1350
|
-
},
|
|
1351
|
-
ssr: {
|
|
1352
|
-
noExternal: ["@fluid-app/portal-sdk"],
|
|
1353
|
-
resolve: {
|
|
1354
|
-
conditions: ["fluid-widget-authoring", "module", "node", "development|production"],
|
|
1355
|
-
},
|
|
1356
|
-
},
|
|
1357
|
-
});
|
|
1358
|
-
|
|
1359
|
-
try {
|
|
1360
|
-
const widgetConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
|
|
1361
|
-
|
|
1362
|
-
function isSourceWidgetPackage(value) {
|
|
1363
|
-
return Boolean(value && typeof value === "object" && value[SOURCE_PACKAGE_MARKER] === true);
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
function collectSourceWidgetPackages(mod) {
|
|
1367
|
-
const candidates = [
|
|
1368
|
-
mod.widgetPackage,
|
|
1369
|
-
...(Array.isArray(mod.widgetPackages) ? mod.widgetPackages : []),
|
|
1370
|
-
mod.default,
|
|
1371
|
-
];
|
|
1372
|
-
const byPackageId = new Map();
|
|
1373
|
-
for (const candidate of candidates) {
|
|
1374
|
-
if (!isSourceWidgetPackage(candidate)) continue;
|
|
1375
|
-
if (!byPackageId.has(candidate.packageId)) byPackageId.set(candidate.packageId, candidate);
|
|
1376
|
-
}
|
|
1377
|
-
return Array.from(byPackageId.values());
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
function serializeWidget(widget) {
|
|
1381
|
-
if (!widget || typeof widget !== "object" || Array.isArray(widget)) {
|
|
1382
|
-
return widget;
|
|
1383
|
-
}
|
|
1384
|
-
const { component, ...metadata } = widget;
|
|
1385
|
-
return metadata;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
function serializePackage(sourcePackage) {
|
|
1389
|
-
return {
|
|
1390
|
-
manifestVersion: sourcePackage.manifestVersion,
|
|
1391
|
-
scope: sourcePackage.scope,
|
|
1392
|
-
packageStableId: sourcePackage.packageStableId,
|
|
1393
|
-
packageId: sourcePackage.packageId,
|
|
1394
|
-
packageType: sourcePackage.packageType,
|
|
1395
|
-
version: sourcePackage.version,
|
|
1396
|
-
cssUrls: sourcePackage.cssUrls,
|
|
1397
|
-
widgets: Array.isArray(sourcePackage.widgets)
|
|
1398
|
-
? sourcePackage.widgets.map(serializeWidget)
|
|
1399
|
-
: sourcePackage.widgets,
|
|
1400
|
-
};
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
await fs.writeFile(
|
|
1404
|
-
outputPath,
|
|
1405
|
-
JSON.stringify({
|
|
1406
|
-
sentinel: OUTPUT_SENTINEL,
|
|
1407
|
-
data: collectSourceWidgetPackages(widgetConfig).map(serializePackage),
|
|
1408
|
-
}),
|
|
1409
|
-
"utf-8",
|
|
1410
|
-
);
|
|
1411
|
-
} finally {
|
|
1412
|
-
await server.close();
|
|
1413
|
-
}
|
|
1414
|
-
`;
|
|
1415
|
-
}
|
|
1416
|
-
function isRecord$5(value) {
|
|
1417
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1418
|
-
}
|
|
1419
|
-
//#endregion
|
|
1420
|
-
//#region src/utils/extract-manifests.ts
|
|
1421
|
-
/**
|
|
1422
|
-
* Manifest extraction utility
|
|
1423
|
-
*
|
|
1424
|
-
* Extracts serializable widget manifest metadata from portal.config.ts
|
|
1425
|
-
* by writing a minimal wrapper script that imports customWidgets plus
|
|
1426
|
-
* source widget package exports and serializes the result to a temp output
|
|
1427
|
-
* file, then running it with tsx.
|
|
1428
|
-
*
|
|
1429
|
-
* Strips the `component` field (not serializable) from each manifest.
|
|
1430
|
-
*
|
|
1431
|
-
* Writes a temp script, runs it with tsx, and parses JSON output from the
|
|
1432
|
-
* output file. The wrapper loads config modules through Vite SSR so project
|
|
1433
|
-
* aliases and Vite-compatible module resolution are honored.
|
|
1434
|
-
*/
|
|
1435
|
-
const EXTRACT_FILENAME = "extract-manifests.ts";
|
|
1436
|
-
const EXTRACT_OUTPUT_FILENAME = "manifests.json";
|
|
1437
|
-
const EXTRACT_OUTPUT_SENTINEL = "fluid-widget-manifests:v1";
|
|
1438
|
-
const TSX_CLI_PATH = createRequire(import.meta.url).resolve("tsx/cli");
|
|
1439
|
-
/**
|
|
1440
|
-
* Extract serializable widget manifests from a project's portal.config.ts.
|
|
1441
|
-
*
|
|
1442
|
-
* Writes a temp wrapper script, runs it with tsx, and parses the temp JSON
|
|
1443
|
-
* output file. The temp files are always cleaned up.
|
|
1444
|
-
*
|
|
1445
|
-
* Returns an empty array if no customWidgets or source package exports exist.
|
|
1446
|
-
*
|
|
1447
|
-
* Supported static export shapes are intentionally simple so the CLI can avoid
|
|
1448
|
-
* executing unrelated portal configs: named `export const|let|var customWidgets`,
|
|
1449
|
-
* `widgetPackage`, or `widgetPackages`; direct `export default
|
|
1450
|
-
* defineWidgetPackage(...)`; or `export default <identifier>` where that
|
|
1451
|
-
* identifier is initialized with `defineWidgetPackage(...)` in the same file.
|
|
1452
|
-
* Re-export-only and computed export shapes are not detected by this layer.
|
|
1453
|
-
*
|
|
1454
|
-
* @param projectDir - The project root directory containing src/portal.config.ts
|
|
1455
|
-
*/
|
|
1456
|
-
async function extractManifests(projectDir) {
|
|
1457
|
-
let tempDir;
|
|
1458
|
-
try {
|
|
1459
|
-
const config = await resolvePortalWidgetSourceConfig(projectDir);
|
|
1460
|
-
if (!config) return success([]);
|
|
1461
|
-
const configExports = readStaticWidgetExports(await fs.readFile(config.path, "utf-8"));
|
|
1462
|
-
const legacyPortalConfigPath = path$1.join(projectDir, "src", "portal.config.ts");
|
|
1463
|
-
const legacyCustomWidgetsConfigPath = (path$1.resolve(config.path) !== path$1.resolve(legacyPortalConfigPath) && await fs.pathExists(legacyPortalConfigPath) ? readStaticWidgetExports(await fs.readFile(legacyPortalConfigPath, "utf-8")) : void 0)?.hasCustomWidgets ? legacyPortalConfigPath : void 0;
|
|
1464
|
-
if (!configExports.hasCustomWidgets && !configExports.hasSourceWidgetPackages && !legacyCustomWidgetsConfigPath) return success([]);
|
|
1465
|
-
tempDir = await createTempDirectory(projectDir);
|
|
1466
|
-
const extractFile = path$1.join(tempDir, EXTRACT_FILENAME);
|
|
1467
|
-
const outputFile = path$1.join(tempDir, EXTRACT_OUTPUT_FILENAME);
|
|
1468
|
-
const wrapperScript = createManifestExtractorScript({
|
|
1469
|
-
projectDir,
|
|
1470
|
-
widgetConfigPath: config.path,
|
|
1471
|
-
legacyCustomWidgetsConfigPath
|
|
1472
|
-
});
|
|
1473
|
-
await fs.writeFile(extractFile, wrapperScript, {
|
|
1474
|
-
encoding: "utf-8",
|
|
1475
|
-
flag: "wx"
|
|
1476
|
-
});
|
|
1477
|
-
await execa(process.execPath, [
|
|
1478
|
-
TSX_CLI_PATH,
|
|
1479
|
-
extractFile,
|
|
1480
|
-
outputFile
|
|
1481
|
-
], {
|
|
1482
|
-
cwd: projectDir,
|
|
1483
|
-
stdio: "pipe",
|
|
1484
|
-
env: {
|
|
1485
|
-
...process.env,
|
|
1486
|
-
NODE_ENV: "production",
|
|
1487
|
-
NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
|
|
1488
|
-
}
|
|
1489
|
-
});
|
|
1490
|
-
const outputResult = await readManifestExtractorOutput(outputFile);
|
|
1491
|
-
if (!outputResult.success) return outputResult;
|
|
1492
|
-
const parsed = outputResult.value;
|
|
1493
|
-
if (parsed.length === 0) return success([]);
|
|
1494
|
-
return success(parsed.filter((m) => typeof m === "object" && m !== null && typeof m.type === "string" && typeof m.displayName === "string"));
|
|
1495
|
-
} catch (err) {
|
|
1496
|
-
const error = err;
|
|
1497
|
-
return failure({
|
|
1498
|
-
code: "EXTRACTION_FAILED",
|
|
1499
|
-
message: "Failed to extract widget manifests from portal widget source config",
|
|
1500
|
-
details: error.stderr ?? error.message ?? String(err)
|
|
1501
|
-
});
|
|
1502
|
-
} finally {
|
|
1503
|
-
if (tempDir) await fs.remove(tempDir).catch(() => {});
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
function readStaticWidgetExports(source) {
|
|
1507
|
-
const strippedSource = source.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
1508
|
-
const defaultExportMatch = /export\s+default\s+([A-Za-z_$][\w$]*)\b/.exec(strippedSource);
|
|
1509
|
-
const widgetPackageDefinitionNames = new Set(Array.from(strippedSource.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)(?:\s*:[^=]+)?\s*=\s*defineWidgetPackage\s*\(/g), (match) => match[1]));
|
|
1510
|
-
const hasDefaultWidgetPackageExport = /export\s+default\s+defineWidgetPackage\s*\(/.test(strippedSource) || defaultExportMatch !== null && widgetPackageDefinitionNames.has(defaultExportMatch[1]);
|
|
1511
|
-
return {
|
|
1512
|
-
hasCustomWidgets: /export\s+(?:const|let|var)\s+customWidgets\b/.test(strippedSource),
|
|
1513
|
-
hasSourceWidgetPackages: /export\s+(?:const|let|var)\s+widgetPackage\b/.test(strippedSource) || /export\s+(?:const|let|var)\s+widgetPackages\b/.test(strippedSource) || hasDefaultWidgetPackageExport
|
|
1514
|
-
};
|
|
1515
|
-
}
|
|
1516
|
-
async function createTempDirectory(projectDir) {
|
|
1517
|
-
const projectTmpDir = path$1.join(projectDir, ".fluid", "tmp");
|
|
1518
|
-
try {
|
|
1519
|
-
await fs.ensureDir(projectTmpDir);
|
|
1520
|
-
return await fs.mkdtemp(path$1.join(projectTmpDir, "manifests-"));
|
|
1521
|
-
} catch (err) {
|
|
1522
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1523
|
-
throw new Error(`Unable to create project-local temporary directory at ${projectTmpDir}: ${message}`);
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
async function readManifestExtractorOutput(outputFile) {
|
|
1527
|
-
let output;
|
|
1528
|
-
try {
|
|
1529
|
-
output = await fs.readFile(outputFile, "utf-8");
|
|
1530
|
-
} catch (err) {
|
|
1531
|
-
return failure({
|
|
1532
|
-
code: "EXTRACTION_FAILED",
|
|
1533
|
-
message: "Manifest extractor did not write an output file",
|
|
1534
|
-
details: err instanceof Error ? err.message : String(err)
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1537
|
-
let parsed;
|
|
1538
|
-
try {
|
|
1539
|
-
parsed = JSON.parse(output);
|
|
1540
|
-
} catch {
|
|
1541
|
-
return failure({
|
|
1542
|
-
code: "INVALID_FORMAT",
|
|
1543
|
-
message: "Failed to parse manifest output file as JSON",
|
|
1544
|
-
details: `Output was: ${output.slice(0, 200)}`
|
|
1545
|
-
});
|
|
1546
|
-
}
|
|
1547
|
-
if (!isRecord$4(parsed) || parsed.sentinel !== EXTRACT_OUTPUT_SENTINEL) return failure({
|
|
1548
|
-
code: "INVALID_FORMAT",
|
|
1549
|
-
message: "Manifest extractor output file had an invalid sentinel"
|
|
1550
|
-
});
|
|
1551
|
-
if (!Array.isArray(parsed.data)) return failure({
|
|
1552
|
-
code: "INVALID_FORMAT",
|
|
1553
|
-
message: "extracted widget manifests output is not an array",
|
|
1554
|
-
details: `Expected an array, got: ${typeof parsed.data}`
|
|
1555
|
-
});
|
|
1556
|
-
return success(parsed.data);
|
|
1557
|
-
}
|
|
1558
|
-
function isRecord$4(value) {
|
|
1559
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1560
|
-
}
|
|
1561
|
-
function createManifestExtractorScript(options) {
|
|
1562
|
-
return `
|
|
1563
|
-
import fs from "node:fs/promises";
|
|
1564
|
-
import path from "node:path";
|
|
1565
|
-
import { createServer, normalizePath } from "vite";
|
|
1566
|
-
import {
|
|
1567
|
-
isSourceWidgetPackage,
|
|
1568
|
-
sourceWidgetPackagesToManifests,
|
|
1569
|
-
} from "@fluid-app/portal-sdk";
|
|
1570
|
-
|
|
1571
|
-
const OUTPUT_SENTINEL = ${JSON.stringify(EXTRACT_OUTPUT_SENTINEL)};
|
|
1572
|
-
const projectRoot = ${JSON.stringify(options.projectDir)};
|
|
1573
|
-
const widgetConfigPath = ${JSON.stringify(options.widgetConfigPath)};
|
|
1574
|
-
const legacyCustomWidgetsConfigPath = ${JSON.stringify(options.legacyCustomWidgetsConfigPath)};
|
|
1575
|
-
const outputPath = process.argv[2];
|
|
1576
|
-
if (!outputPath) throw new Error("Missing manifest extractor output path.");
|
|
1577
|
-
|
|
1578
|
-
function toViteModuleId(modulePath) {
|
|
1579
|
-
const relativePath = path.relative(projectRoot, modulePath);
|
|
1580
|
-
if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
1581
|
-
return "/" + normalizePath(relativePath);
|
|
1582
|
-
}
|
|
1583
|
-
return normalizePath(modulePath);
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
const server = await createServer({
|
|
1587
|
-
root: projectRoot,
|
|
1588
|
-
mode: "production",
|
|
1589
|
-
server: { middlewareMode: true },
|
|
1590
|
-
appType: "custom",
|
|
1591
|
-
logLevel: "error",
|
|
1592
|
-
clearScreen: false,
|
|
1593
|
-
resolve: {
|
|
1594
|
-
conditions: ["fluid-widget-authoring", "module", "browser", "development|production"],
|
|
1595
|
-
},
|
|
1596
|
-
ssr: {
|
|
1597
|
-
noExternal: ["@fluid-app/portal-sdk"],
|
|
1598
|
-
resolve: {
|
|
1599
|
-
conditions: ["fluid-widget-authoring", "module", "node", "development|production"],
|
|
1600
|
-
},
|
|
1601
|
-
},
|
|
1602
|
-
});
|
|
1603
|
-
|
|
1604
|
-
try {
|
|
1605
|
-
const portalConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
|
|
1606
|
-
const legacyPortalConfig = legacyCustomWidgetsConfigPath
|
|
1607
|
-
? await server.ssrLoadModule(toViteModuleId(legacyCustomWidgetsConfigPath))
|
|
1608
|
-
: {};
|
|
1609
|
-
|
|
1610
|
-
const sourcePackages = [];
|
|
1611
|
-
const seenSourcePackageIds = new Set();
|
|
1612
|
-
for (const candidate of [
|
|
1613
|
-
portalConfig.widgetPackage,
|
|
1614
|
-
...(Array.isArray(portalConfig.widgetPackages) ? portalConfig.widgetPackages : []),
|
|
1615
|
-
portalConfig.default,
|
|
1616
|
-
]) {
|
|
1617
|
-
if (!isSourceWidgetPackage(candidate)) continue;
|
|
1618
|
-
if (seenSourcePackageIds.has(candidate.packageId)) continue;
|
|
1619
|
-
seenSourcePackageIds.add(candidate.packageId);
|
|
1620
|
-
sourcePackages.push(candidate);
|
|
1621
|
-
}
|
|
1622
|
-
const manifests = [
|
|
1623
|
-
...(Array.isArray(portalConfig.customWidgets) ? portalConfig.customWidgets : []),
|
|
1624
|
-
...(Array.isArray(legacyPortalConfig.customWidgets) ? legacyPortalConfig.customWidgets : []),
|
|
1625
|
-
...sourceWidgetPackagesToManifests(sourcePackages),
|
|
1626
|
-
];
|
|
1627
|
-
const serializable = manifests.map(({ component, ...rest }) => rest);
|
|
1628
|
-
await fs.writeFile(
|
|
1629
|
-
outputPath,
|
|
1630
|
-
JSON.stringify({ sentinel: OUTPUT_SENTINEL, data: serializable }),
|
|
1631
|
-
"utf-8",
|
|
1632
|
-
);
|
|
1633
|
-
} finally {
|
|
1634
|
-
await server.close();
|
|
1635
|
-
}
|
|
1636
|
-
`;
|
|
1637
|
-
}
|
|
1638
|
-
//#endregion
|
|
1639
|
-
//#region src/commands/build.ts
|
|
1640
|
-
const buildCommand = new Command("build").description("Build the application for production").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
|
|
1641
|
-
const cwd = process.cwd();
|
|
1642
|
-
if (!existsSync(join(cwd, "package.json"))) {
|
|
1643
|
-
console.error(chalk.red("Error: No package.json found in current directory"));
|
|
1644
|
-
console.error(chalk.yellow("Make sure you're in a Fluid project directory"));
|
|
1645
|
-
process.exit(1);
|
|
1646
|
-
}
|
|
1647
|
-
if (!existsSync(join(cwd, "vite.config.ts"))) {
|
|
1648
|
-
console.error(chalk.red("Error: No vite.config.ts found"));
|
|
1649
|
-
console.error(chalk.yellow("This command must be run from a Fluid project directory"));
|
|
1650
|
-
process.exit(1);
|
|
1651
|
-
}
|
|
1652
|
-
console.log();
|
|
1653
|
-
console.log(chalk.bold("Building for production..."));
|
|
1654
|
-
console.log();
|
|
1655
|
-
const spinner = ora("Building...").start();
|
|
1656
|
-
try {
|
|
1657
|
-
await execa("pnpm", ["run", "build"], {
|
|
1658
|
-
cwd,
|
|
1659
|
-
stdio: "pipe"
|
|
1660
|
-
});
|
|
1661
|
-
spinner.succeed("Build completed");
|
|
1662
|
-
const manifestSpinner = ora("Extracting widget manifests...").start();
|
|
1663
|
-
const outDir = options.outDir ?? "dist";
|
|
1664
|
-
const manifestResult = await extractManifests(cwd);
|
|
1665
|
-
const manifestPath = [join(cwd, outDir, "__manifests__.json"), join(cwd, outDir, "public", "__manifests__.json")].find((p) => existsSync(p));
|
|
1666
|
-
if (manifestResult.success) if (manifestResult.value.length === 0) manifestSpinner.info("No custom widgets found");
|
|
1667
|
-
else if (!manifestPath) manifestSpinner.warn(`__manifests__.json not found in build output — skipping manifest write`);
|
|
1668
|
-
else {
|
|
1669
|
-
writeFileSync(manifestPath, JSON.stringify(manifestResult.value));
|
|
1670
|
-
manifestSpinner.succeed(`Extracted ${manifestResult.value.length} widget manifest(s)`);
|
|
1671
|
-
}
|
|
1672
|
-
else manifestSpinner.warn(`Manifest extraction failed: ${manifestResult.error.message}`);
|
|
1673
|
-
console.log();
|
|
1674
|
-
console.log(`Output written to ${chalk.cyan(outDir)}/`);
|
|
1675
|
-
console.log();
|
|
1676
|
-
console.log("To preview the build locally:");
|
|
1677
|
-
console.log(chalk.cyan(" pnpm vite preview"));
|
|
1678
|
-
console.log();
|
|
1679
|
-
} catch (error) {
|
|
1680
|
-
spinner.fail("Build failed");
|
|
1681
|
-
const execaError = error;
|
|
1682
|
-
if (execaError.stderr) console.error(execaError.stderr);
|
|
1683
|
-
process.exit(1);
|
|
1684
|
-
}
|
|
1685
|
-
});
|
|
1686
|
-
//#endregion
|
|
1687
|
-
//#region src/utils/push-validation.ts
|
|
1688
|
-
/**
|
|
1689
|
-
* Cross-reference validation and change categorization utilities for the push command.
|
|
1690
|
-
*
|
|
1691
|
-
* Extracted into a standalone utility so that pure logic can be tested
|
|
1692
|
-
* without pulling in CLI dependencies (ora, chalk, prompts, etc.).
|
|
1693
|
-
*/
|
|
1694
|
-
/**
|
|
1695
|
-
* Extract the slug from a file path (e.g., "screens/home.json" -> "home").
|
|
1696
|
-
*/
|
|
1697
|
-
function slugFromPath(filePath) {
|
|
1698
|
-
return basename(filePath, ".json");
|
|
1699
|
-
}
|
|
1700
|
-
/**
|
|
1701
|
-
* Extract the resource type directory from a file path (e.g., "screens/home.json" -> "screens").
|
|
1702
|
-
*/
|
|
1703
|
-
function resourceTypeFromPath(filePath) {
|
|
1704
|
-
const dir = filePath.split("/")[0];
|
|
1705
|
-
if (dir === "screens" || dir === "themes" || dir === "navigations" || dir === "profiles") return dir;
|
|
1706
|
-
return null;
|
|
1707
|
-
}
|
|
1708
|
-
/**
|
|
1709
|
-
* Read and parse a JSON file from the portal directory.
|
|
1710
|
-
*/
|
|
1711
|
-
async function readPortalFile$1(portalDir, relativePath) {
|
|
1712
|
-
const content = await readFile(join(portalDir, relativePath), "utf-8");
|
|
1713
|
-
return JSON.parse(content);
|
|
1714
|
-
}
|
|
1715
|
-
/**
|
|
1716
|
-
* Categorize a snapshot diff into resource-type-specific change lists.
|
|
1717
|
-
*/
|
|
1718
|
-
function categorizeChanges(diff) {
|
|
1719
|
-
const result = {
|
|
1720
|
-
screens: {
|
|
1721
|
-
new: [],
|
|
1722
|
-
changed: [],
|
|
1723
|
-
deleted: []
|
|
1724
|
-
},
|
|
1725
|
-
themes: {
|
|
1726
|
-
new: [],
|
|
1727
|
-
changed: [],
|
|
1728
|
-
deleted: []
|
|
1729
|
-
},
|
|
1730
|
-
navigations: {
|
|
1731
|
-
new: [],
|
|
1732
|
-
changed: [],
|
|
1733
|
-
deleted: []
|
|
1734
|
-
},
|
|
1735
|
-
profiles: {
|
|
1736
|
-
new: [],
|
|
1737
|
-
changed: [],
|
|
1738
|
-
deleted: []
|
|
1739
|
-
}
|
|
1740
|
-
};
|
|
1741
|
-
for (const file of diff.new) {
|
|
1742
|
-
const type = resourceTypeFromPath(file);
|
|
1743
|
-
if (type) result[type].new.push(file);
|
|
1744
|
-
}
|
|
1745
|
-
for (const file of diff.changed) {
|
|
1746
|
-
const type = resourceTypeFromPath(file);
|
|
1747
|
-
if (type) result[type].changed.push(file);
|
|
1748
|
-
}
|
|
1749
|
-
for (const file of diff.deleted) {
|
|
1750
|
-
const type = resourceTypeFromPath(file);
|
|
1751
|
-
if (type) result[type].deleted.push(file);
|
|
1752
|
-
}
|
|
1753
|
-
return result;
|
|
1754
|
-
}
|
|
1755
|
-
/**
|
|
1756
|
-
* Validate that all cross-references between local portal files are valid.
|
|
1757
|
-
*
|
|
1758
|
-
* Checks:
|
|
1759
|
-
* - Navigation items' "screen" slugs reference existing screen files or mappings
|
|
1760
|
-
* - Profile "navigation" and "mobile_navigation" slugs reference existing nav files or mappings
|
|
1761
|
-
* - Profile "themes" slugs reference existing theme files or mappings
|
|
1762
|
-
*/
|
|
1763
|
-
async function validateCrossReferences(portalDir, mappings, changes) {
|
|
1764
|
-
const errors = [];
|
|
1765
|
-
const validScreenSlugs = buildValidSlugsSet(portalDir, "screens", mappings);
|
|
1766
|
-
const validNavSlugs = buildValidSlugsSet(portalDir, "navigations", mappings);
|
|
1767
|
-
const validThemeSlugs = buildValidSlugsSet(portalDir, "themes", mappings);
|
|
1768
|
-
for (const file of changes.screens.deleted) validScreenSlugs.delete(slugFromPath(file));
|
|
1769
|
-
for (const file of changes.navigations.deleted) validNavSlugs.delete(slugFromPath(file));
|
|
1770
|
-
for (const file of changes.themes.deleted) validThemeSlugs.delete(slugFromPath(file));
|
|
1771
|
-
const navFilesToCheck = [...changes.navigations.new, ...changes.navigations.changed];
|
|
1772
|
-
for (const file of navFilesToCheck) try {
|
|
1773
|
-
validateNavigationItems((await readPortalFile$1(portalDir, file)).navigation_items, file, validScreenSlugs, errors);
|
|
1774
|
-
} catch {
|
|
1775
|
-
errors.push({
|
|
1776
|
-
file,
|
|
1777
|
-
message: "Failed to read navigation file"
|
|
1778
|
-
});
|
|
1779
|
-
}
|
|
1780
|
-
const profileFilesToCheck = [...changes.profiles.new, ...changes.profiles.changed];
|
|
1781
|
-
for (const file of profileFilesToCheck) try {
|
|
1782
|
-
const profile = await readPortalFile$1(portalDir, file);
|
|
1783
|
-
if (profile.navigation && !validNavSlugs.has(profile.navigation)) errors.push({
|
|
1784
|
-
file,
|
|
1785
|
-
message: `References navigation "${profile.navigation}" which does not exist`
|
|
1786
|
-
});
|
|
1787
|
-
if (profile.mobile_navigation && !validNavSlugs.has(profile.mobile_navigation)) errors.push({
|
|
1788
|
-
file,
|
|
1789
|
-
message: `References mobile_navigation "${profile.mobile_navigation}" which does not exist`
|
|
1790
|
-
});
|
|
1791
|
-
for (const themeSlug of profile.themes) if (!validThemeSlugs.has(themeSlug)) errors.push({
|
|
1792
|
-
file,
|
|
1793
|
-
message: `References theme "${themeSlug}" which does not exist`
|
|
1794
|
-
});
|
|
1795
|
-
} catch {
|
|
1796
|
-
errors.push({
|
|
1797
|
-
file,
|
|
1798
|
-
message: "Failed to read profile file"
|
|
1799
|
-
});
|
|
1800
|
-
}
|
|
1801
|
-
return errors;
|
|
1802
|
-
}
|
|
1803
|
-
/**
|
|
1804
|
-
* Build a set of valid slugs for a resource type by combining
|
|
1805
|
-
* existing mapping slugs with local file slugs on disk.
|
|
1806
|
-
*/
|
|
1807
|
-
function buildValidSlugsSet(portalDir, resourceType, mappings) {
|
|
1808
|
-
const slugs = /* @__PURE__ */ new Set();
|
|
1809
|
-
for (const slug of Object.keys(mappings[resourceType])) slugs.add(slug);
|
|
1810
|
-
const dir = join(portalDir, resourceType);
|
|
1811
|
-
if (existsSync(dir)) try {
|
|
1812
|
-
const entries = readdirSync(dir);
|
|
1813
|
-
for (const entry of entries) if (entry.endsWith(".json")) slugs.add(basename(entry, ".json"));
|
|
1814
|
-
} catch {}
|
|
1815
|
-
return slugs;
|
|
1362
|
+
/**
|
|
1363
|
+
* Build a set of valid slugs for a resource type by combining
|
|
1364
|
+
* existing mapping slugs with local file slugs on disk.
|
|
1365
|
+
*/
|
|
1366
|
+
function buildValidSlugsSet(portalDir, resourceType, mappings) {
|
|
1367
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
1368
|
+
for (const slug of Object.keys(mappings[resourceType])) slugs.add(slug);
|
|
1369
|
+
const dir = join(portalDir, resourceType);
|
|
1370
|
+
if (existsSync(dir)) try {
|
|
1371
|
+
const entries = readdirSync(dir);
|
|
1372
|
+
for (const entry of entries) if (entry.endsWith(".json")) slugs.add(basename(entry, ".json"));
|
|
1373
|
+
} catch {}
|
|
1374
|
+
return slugs;
|
|
1816
1375
|
}
|
|
1817
1376
|
/**
|
|
1818
1377
|
* Recursively validate navigation item screen references.
|
|
@@ -2094,7 +1653,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
|
|
|
2094
1653
|
const slug = slugFromPath(file);
|
|
2095
1654
|
try {
|
|
2096
1655
|
const local = await readPortalFile(portalDir, file);
|
|
2097
|
-
const newId = (await fluid_os_v0_create_fluid_osscreen(client, defId, { screen: {
|
|
1656
|
+
const newId = (await fluidOs.fluid_os_v0_create_fluid_osscreen(client, defId, { screen: {
|
|
2098
1657
|
name: local.name,
|
|
2099
1658
|
slug,
|
|
2100
1659
|
component_tree: toApiComponentTree(local.component_tree)
|
|
@@ -2135,7 +1694,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
|
|
|
2135
1694
|
}
|
|
2136
1695
|
try {
|
|
2137
1696
|
const local = await readPortalFile(portalDir, file);
|
|
2138
|
-
await fluid_os_v0_update_fluid_osscreen(client, defId, screenId, { screen: {
|
|
1697
|
+
await fluidOs.fluid_os_v0_update_fluid_osscreen(client, defId, screenId, { screen: {
|
|
2139
1698
|
name: local.name,
|
|
2140
1699
|
slug,
|
|
2141
1700
|
component_tree: toApiComponentTree(local.component_tree)
|
|
@@ -2174,7 +1733,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
|
|
|
2174
1733
|
};
|
|
2175
1734
|
}
|
|
2176
1735
|
try {
|
|
2177
|
-
await fluid_os_v0_delete_fluid_osscreen(client, defId, screenId);
|
|
1736
|
+
await fluidOs.fluid_os_v0_delete_fluid_osscreen(client, defId, screenId);
|
|
2178
1737
|
currentMappings = removeMapping(currentMappings, "screens", slug);
|
|
2179
1738
|
results.push({
|
|
2180
1739
|
file,
|
|
@@ -2209,7 +1768,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
|
|
|
2209
1768
|
const slug = slugFromPath(file);
|
|
2210
1769
|
try {
|
|
2211
1770
|
const local = await readPortalFile(portalDir, file);
|
|
2212
|
-
const newId = (await fluid_os_v0_create_fluid_ostheme(client, defId, { theme: {
|
|
1771
|
+
const newId = (await fluidOs.fluid_os_v0_create_fluid_ostheme(client, defId, { theme: {
|
|
2213
1772
|
name: local.name,
|
|
2214
1773
|
active: local.active,
|
|
2215
1774
|
config: local.config
|
|
@@ -2250,7 +1809,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
|
|
|
2250
1809
|
}
|
|
2251
1810
|
try {
|
|
2252
1811
|
const local = await readPortalFile(portalDir, file);
|
|
2253
|
-
await fluid_os_v0_update_fluid_ostheme(client, defId, themeId, { theme: {
|
|
1812
|
+
await fluidOs.fluid_os_v0_update_fluid_ostheme(client, defId, themeId, { theme: {
|
|
2254
1813
|
name: local.name,
|
|
2255
1814
|
active: local.active,
|
|
2256
1815
|
config: local.config
|
|
@@ -2289,7 +1848,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
|
|
|
2289
1848
|
};
|
|
2290
1849
|
}
|
|
2291
1850
|
try {
|
|
2292
|
-
await fluid_os_v0_delete_fluid_ostheme(client, defId, themeId);
|
|
1851
|
+
await fluidOs.fluid_os_v0_delete_fluid_ostheme(client, defId, themeId);
|
|
2293
1852
|
currentMappings = removeMapping(currentMappings, "themes", slug);
|
|
2294
1853
|
results.push({
|
|
2295
1854
|
file,
|
|
@@ -2359,7 +1918,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2359
1918
|
const slug = slugFromPath(file);
|
|
2360
1919
|
try {
|
|
2361
1920
|
const local = await readPortalFile(portalDir, file);
|
|
2362
|
-
const newId = (await fluid_os_v0_create_fluid_osnavigation(client, defId, { navigation: {
|
|
1921
|
+
const newId = (await fluidOs.fluid_os_v0_create_fluid_osnavigation(client, defId, { navigation: {
|
|
2363
1922
|
name: local.name,
|
|
2364
1923
|
platform: local.platform
|
|
2365
1924
|
} })).navigation?.id;
|
|
@@ -2369,7 +1928,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2369
1928
|
const localToServerId = /* @__PURE__ */ new Map();
|
|
2370
1929
|
for (const item of resolvedItems) {
|
|
2371
1930
|
const resolvedParentId = item.parent_id != null ? localToServerId.get(item.parent_id) ?? item.parent_id : void 0;
|
|
2372
|
-
const created = await fluid_os_v0_create_fluid_osnavigation_item(client, defId, newId, { navigation_item: {
|
|
1931
|
+
const created = await fluidOs.fluid_os_v0_create_fluid_osnavigation_item(client, defId, newId, { navigation_item: {
|
|
2373
1932
|
label: item.label ?? "",
|
|
2374
1933
|
position: item.position ?? 0,
|
|
2375
1934
|
icon: item.icon ?? void 0,
|
|
@@ -2416,15 +1975,15 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2416
1975
|
}
|
|
2417
1976
|
try {
|
|
2418
1977
|
const local = await readPortalFile(portalDir, file);
|
|
2419
|
-
await fluid_os_v0_update_fluid_osnavigation(client, defId, navId, { navigation: {
|
|
1978
|
+
await fluidOs.fluid_os_v0_update_fluid_osnavigation(client, defId, navId, { navigation: {
|
|
2420
1979
|
name: local.name,
|
|
2421
1980
|
platform: local.platform
|
|
2422
1981
|
} });
|
|
2423
1982
|
const resolvedItems = flattenNavigationItems(resolveNavigationItemScreenIds(local.navigation_items, currentMappings));
|
|
2424
|
-
const serverItems = (await fluid_os_v0_list_fluid_osnavigation_items(client, defId, navId)).navigation_items ?? [];
|
|
1983
|
+
const serverItems = (await fluidOs.fluid_os_v0_list_fluid_osnavigation_items(client, defId, navId)).navigation_items ?? [];
|
|
2425
1984
|
const serverById = new Map(serverItems.map((s) => [s.id, s]));
|
|
2426
1985
|
const localIds = new Set(resolvedItems.filter((i) => i.id).map((i) => i.id));
|
|
2427
|
-
for (const serverItem of serverItems) if (!localIds.has(serverItem.id)) await fluid_os_v0_delete_fluid_osnavigation_item(client, defId, navId, serverItem.id);
|
|
1986
|
+
for (const serverItem of serverItems) if (!localIds.has(serverItem.id)) await fluidOs.fluid_os_v0_delete_fluid_osnavigation_item(client, defId, navId, serverItem.id);
|
|
2428
1987
|
const localToServerId = /* @__PURE__ */ new Map();
|
|
2429
1988
|
for (const item of resolvedItems) {
|
|
2430
1989
|
const resolvedParentId = item.parent_id != null ? localToServerId.get(item.parent_id) ?? item.parent_id : void 0;
|
|
@@ -2437,9 +1996,9 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2437
1996
|
source: item.source ?? void 0,
|
|
2438
1997
|
parent_id: resolvedParentId
|
|
2439
1998
|
};
|
|
2440
|
-
if (item.id && serverById.has(item.id)) await fluid_os_v0_update_fluid_osnavigation_item(client, defId, navId, item.id, { navigation_item: body });
|
|
1999
|
+
if (item.id && serverById.has(item.id)) await fluidOs.fluid_os_v0_update_fluid_osnavigation_item(client, defId, navId, item.id, { navigation_item: body });
|
|
2441
2000
|
else {
|
|
2442
|
-
const created = await fluid_os_v0_create_fluid_osnavigation_item(client, defId, navId, { navigation_item: {
|
|
2001
|
+
const created = await fluidOs.fluid_os_v0_create_fluid_osnavigation_item(client, defId, navId, { navigation_item: {
|
|
2443
2002
|
...body,
|
|
2444
2003
|
label: body.label ?? "",
|
|
2445
2004
|
position: body.position ?? 0
|
|
@@ -2481,7 +2040,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2481
2040
|
};
|
|
2482
2041
|
}
|
|
2483
2042
|
try {
|
|
2484
|
-
await fluid_os_v0_delete_fluid_osnavigation(client, defId, navId);
|
|
2043
|
+
await fluidOs.fluid_os_v0_delete_fluid_osnavigation(client, defId, navId);
|
|
2485
2044
|
currentMappings = removeMapping(currentMappings, "navigations", slug);
|
|
2486
2045
|
results.push({
|
|
2487
2046
|
file,
|
|
@@ -2515,7 +2074,8 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
|
|
|
2515
2074
|
for (const file of changes.new) {
|
|
2516
2075
|
const slug = slugFromPath(file);
|
|
2517
2076
|
try {
|
|
2518
|
-
const
|
|
2077
|
+
const body = resolveProfileBody(await readPortalFile(portalDir, file), currentMappings);
|
|
2078
|
+
const newId = (await fluidOs.fluid_os_v0_create_fluid_osprofile(client, defId, { profile: body })).profile?.id;
|
|
2519
2079
|
if (newId != null) currentMappings = updateMapping(currentMappings, "profiles", slug, newId);
|
|
2520
2080
|
results.push({
|
|
2521
2081
|
file,
|
|
@@ -2551,7 +2111,8 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
|
|
|
2551
2111
|
};
|
|
2552
2112
|
}
|
|
2553
2113
|
try {
|
|
2554
|
-
|
|
2114
|
+
const body = resolveProfileBody(await readPortalFile(portalDir, file), currentMappings);
|
|
2115
|
+
await fluidOs.fluid_os_v0_update_fluid_osprofile(client, defId, profileId, { profile: body });
|
|
2555
2116
|
results.push({
|
|
2556
2117
|
file,
|
|
2557
2118
|
action: "updated",
|
|
@@ -2586,7 +2147,7 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
|
|
|
2586
2147
|
};
|
|
2587
2148
|
}
|
|
2588
2149
|
try {
|
|
2589
|
-
await fluid_os_v0_delete_fluid_osprofile(client, defId, profileId);
|
|
2150
|
+
await fluidOs.fluid_os_v0_delete_fluid_osprofile(client, defId, profileId);
|
|
2590
2151
|
currentMappings = removeMapping(currentMappings, "profiles", slug);
|
|
2591
2152
|
results.push({
|
|
2592
2153
|
file,
|
|
@@ -2843,7 +2404,7 @@ async function selectDefinition(client, appName) {
|
|
|
2843
2404
|
const definitions = [];
|
|
2844
2405
|
let page = 1;
|
|
2845
2406
|
while (true) {
|
|
2846
|
-
const response = await fluid_os_v0_list_fluid_osdefinitions(client, {
|
|
2407
|
+
const response = await fluidOs.fluid_os_v0_list_fluid_osdefinitions(client, {
|
|
2847
2408
|
page,
|
|
2848
2409
|
per_page: 500
|
|
2849
2410
|
});
|
|
@@ -2988,138 +2549,340 @@ function insertImport(source, importLine) {
|
|
|
2988
2549
|
if (lineEnd === -1) return source + "\n" + importLine + "\n";
|
|
2989
2550
|
return source.slice(0, lineEnd + 1) + importLine + "\n" + source.slice(lineEnd + 1);
|
|
2990
2551
|
}
|
|
2991
|
-
/**
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
* matches inside JSDoc @example blocks.
|
|
2998
|
-
*/
|
|
2999
|
-
function insertIntoCustomWidgets(source, camelName) {
|
|
3000
|
-
const pattern = /(export const customWidgets(?::[\s\S]*?)?\s*=\s*)\[([^\]]*)\]/g;
|
|
2552
|
+
/** Insert a widget definition into a defineWidgetPackage widgets array. */
|
|
2553
|
+
function insertIntoWidgetPackage(source, exportName) {
|
|
2554
|
+
const packageStart = source.lastIndexOf("defineWidgetPackage(");
|
|
2555
|
+
if (packageStart === -1) return null;
|
|
2556
|
+
const packageSource = source.slice(packageStart);
|
|
2557
|
+
const pattern = /(widgets\s*:\s*)\[([^\]]*)\]/g;
|
|
3001
2558
|
let lastMatch = null;
|
|
3002
2559
|
let m;
|
|
3003
|
-
while ((m = pattern.exec(
|
|
2560
|
+
while ((m = pattern.exec(packageSource)) !== null) lastMatch = m;
|
|
3004
2561
|
if (!lastMatch) return null;
|
|
3005
2562
|
const fullMatch = lastMatch[0];
|
|
3006
2563
|
const declaration = lastMatch[1];
|
|
3007
2564
|
const inner = lastMatch[2];
|
|
3008
|
-
const matchStart = lastMatch.index;
|
|
2565
|
+
const matchStart = packageStart + lastMatch.index;
|
|
3009
2566
|
const lines = inner.split("\n");
|
|
3010
2567
|
const existingEntries = [];
|
|
3011
2568
|
for (const line of lines) {
|
|
3012
2569
|
const trimmed = line.trim();
|
|
3013
2570
|
if (trimmed && !trimmed.startsWith("//")) existingEntries.push(trimmed.replace(/,$/, ""));
|
|
3014
2571
|
}
|
|
3015
|
-
if (existingEntries.includes(
|
|
3016
|
-
const commentLines = lines.filter((line) => line.trim().startsWith("//")).map((line) => line.trimEnd());
|
|
3017
|
-
const replacement = `${declaration}[${commentLines.length > 0 ? "\n" + commentLines.join("\n") : ""}\n${[...existingEntries,
|
|
3018
|
-
return source.slice(0, matchStart) + replacement + source.slice(matchStart + fullMatch.length);
|
|
2572
|
+
if (existingEntries.includes(exportName)) return source;
|
|
2573
|
+
const commentLines = lines.filter((line) => line.trim().startsWith("//")).map((line) => line.trimEnd());
|
|
2574
|
+
const replacement = `${declaration}[${commentLines.length > 0 ? "\n" + commentLines.join("\n") : ""}\n${[...existingEntries, exportName].map((e) => ` ${e},`).join("\n")}\n]`;
|
|
2575
|
+
return source.slice(0, matchStart) + replacement + source.slice(matchStart + fullMatch.length);
|
|
2576
|
+
}
|
|
2577
|
+
//#endregion
|
|
2578
|
+
//#region src/commands/widget-create.ts
|
|
2579
|
+
const WIDGET_CONFIG_PATH$1 = path.join("src", "widgets.config.ts");
|
|
2580
|
+
const createSubcommand = new Command("create").description("Scaffold a new Remote DOM widget").argument("<name>", "Widget name in kebab-case (e.g., stock-ticker)").option("-c, --category <category>", "Widget category for palette grouping", "components").action(async (name, options) => {
|
|
2581
|
+
const cwd = process.cwd();
|
|
2582
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
2583
|
+
const configPath = path.join(cwd, WIDGET_CONFIG_PATH$1);
|
|
2584
|
+
const widgetDir = path.join(cwd, "src", "widgets", name);
|
|
2585
|
+
const validationError = validateWidgetName(name);
|
|
2586
|
+
if (validationError) {
|
|
2587
|
+
console.error(chalk.red(`Error: ${validationError}`));
|
|
2588
|
+
process.exit(1);
|
|
2589
|
+
}
|
|
2590
|
+
if (!await fs.pathExists(packageJsonPath)) {
|
|
2591
|
+
console.error(chalk.red("Error: No package.json found."));
|
|
2592
|
+
console.error(chalk.yellow("Run this command from a Fluid portal project root."));
|
|
2593
|
+
process.exit(1);
|
|
2594
|
+
}
|
|
2595
|
+
if (await fs.pathExists(widgetDir)) {
|
|
2596
|
+
console.error(chalk.red(`Error: Widget directory already exists: src/widgets/${name}`));
|
|
2597
|
+
process.exit(1);
|
|
2598
|
+
}
|
|
2599
|
+
const widgetType = toWidgetType(name);
|
|
2600
|
+
const componentName = toComponentName(widgetType);
|
|
2601
|
+
const camelName = toCamelCase(name);
|
|
2602
|
+
const exportName = camelName.endsWith("Widget") ? camelName : `${camelName}Widget`;
|
|
2603
|
+
const displayName = toDisplayName(name);
|
|
2604
|
+
const category = options.category ?? "components";
|
|
2605
|
+
const importLine = `import { ${exportName} } from "./widgets/${name}";`;
|
|
2606
|
+
try {
|
|
2607
|
+
await fs.ensureDir(widgetDir);
|
|
2608
|
+
await fs.writeFile(path.join(widgetDir, "index.tsx"), createWidgetSource({
|
|
2609
|
+
name,
|
|
2610
|
+
widgetType,
|
|
2611
|
+
componentName,
|
|
2612
|
+
exportName,
|
|
2613
|
+
displayName,
|
|
2614
|
+
category
|
|
2615
|
+
}), "utf-8");
|
|
2616
|
+
if (await fs.pathExists(configPath)) {
|
|
2617
|
+
const configSource = await fs.readFile(configPath, "utf-8");
|
|
2618
|
+
if (isEmptyWidgetConfig(configSource)) await fs.writeFile(configPath, createWidgetConfigSource(importLine, exportName), "utf-8");
|
|
2619
|
+
else {
|
|
2620
|
+
const withImport = insertImport(configSource, importLine);
|
|
2621
|
+
const updated = withImport ? insertIntoWidgetPackage(withImport, exportName) : null;
|
|
2622
|
+
if (!updated) throw new Error(`${WIDGET_CONFIG_PATH$1} must export the starter widgetPackages placeholder or contain a defineWidgetPackage({ widgets: [...] }) declaration.`);
|
|
2623
|
+
await fs.writeFile(configPath, updated, "utf-8");
|
|
2624
|
+
}
|
|
2625
|
+
} else await fs.writeFile(configPath, createWidgetConfigSource(importLine, exportName), "utf-8");
|
|
2626
|
+
} catch (error) {
|
|
2627
|
+
await fs.remove(widgetDir).catch(() => {});
|
|
2628
|
+
console.error(chalk.red("Error: Failed to scaffold Remote DOM widget."));
|
|
2629
|
+
console.error(error);
|
|
2630
|
+
process.exit(1);
|
|
2631
|
+
}
|
|
2632
|
+
console.log();
|
|
2633
|
+
console.log(chalk.green("Created widget:") + ` src/widgets/${name}/index.tsx`);
|
|
2634
|
+
console.log(chalk.green("Registered") + ` in ${chalk.cyan(WIDGET_CONFIG_PATH$1)}`);
|
|
2635
|
+
console.log();
|
|
2636
|
+
console.log(chalk.yellow("Next steps:"));
|
|
2637
|
+
console.log(` 1. Edit ${chalk.cyan(`src/widgets/${name}/index.tsx`)}`);
|
|
2638
|
+
console.log(` 2. Run ${chalk.cyan("fluid portal deploy --dry-run")} to verify the package`);
|
|
2639
|
+
});
|
|
2640
|
+
function createWidgetSource(options) {
|
|
2641
|
+
return `import { defineWidget } from "@fluid-app/portal-sdk/widgets/worker";
|
|
2642
|
+
|
|
2643
|
+
interface ${options.widgetType}Props {
|
|
2644
|
+
readonly title?: string;
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
function ${options.componentName}({ title = ${JSON.stringify(options.displayName)} }: ${options.widgetType}Props) {
|
|
2648
|
+
return (
|
|
2649
|
+
<section>
|
|
2650
|
+
<h3>{title}</h3>
|
|
2651
|
+
<p>Edit this widget in src/widgets/${options.name}/index.tsx</p>
|
|
2652
|
+
</section>
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
export const ${options.exportName} = defineWidget({
|
|
2657
|
+
name: ${JSON.stringify(options.componentName)},
|
|
2658
|
+
component: ${options.componentName},
|
|
2659
|
+
displayName: ${JSON.stringify(options.displayName)},
|
|
2660
|
+
description: ${JSON.stringify(`A custom ${options.displayName.toLowerCase()} widget`)},
|
|
2661
|
+
icon: "puzzle-piece",
|
|
2662
|
+
category: ${JSON.stringify(options.category)},
|
|
2663
|
+
propertySchema: {
|
|
2664
|
+
fields: [{ key: "title", label: "Title", type: "text" }],
|
|
2665
|
+
},
|
|
2666
|
+
defaultProps: {
|
|
2667
|
+
title: ${JSON.stringify(options.displayName)},
|
|
2668
|
+
},
|
|
2669
|
+
});
|
|
2670
|
+
`;
|
|
2671
|
+
}
|
|
2672
|
+
function isEmptyWidgetConfig(source) {
|
|
2673
|
+
return /export\s+const\s+widgetPackages\s*=\s*\[\]\s+as\s+const\s*;/u.test(source);
|
|
2674
|
+
}
|
|
2675
|
+
function createWidgetConfigSource(importLine, exportName) {
|
|
2676
|
+
return `import { defineWidgetPackage } from "@fluid-app/portal-sdk/widgets/worker";
|
|
2677
|
+
${importLine}
|
|
2678
|
+
|
|
2679
|
+
export const widgetPackage = defineWidgetPackage({
|
|
2680
|
+
scope: "company",
|
|
2681
|
+
packageStableId: "portal-widgets",
|
|
2682
|
+
version: "0.0.0-dev",
|
|
2683
|
+
widgets: [${exportName}],
|
|
2684
|
+
});
|
|
2685
|
+
`;
|
|
2686
|
+
}
|
|
2687
|
+
const widgetCommand = new Command("widget").description("Manage Remote DOM portal widgets").addCommand(createSubcommand);
|
|
2688
|
+
//#endregion
|
|
2689
|
+
//#region src/utils/widget-package-config.ts
|
|
2690
|
+
const WIDGET_CONFIG_PATH = "src/widgets.config.ts";
|
|
2691
|
+
const SOURCE_PACKAGE_EXTRACT_FILENAME = "extract-widget-packages.ts";
|
|
2692
|
+
const SOURCE_PACKAGE_OUTPUT_FILENAME = "source-widget-packages.json";
|
|
2693
|
+
const SOURCE_PACKAGE_OUTPUT_SENTINEL = "fluid-widget-source-packages:v1";
|
|
2694
|
+
const TSX_CLI_PATH = createRequire(import.meta.url).resolve("tsx/cli");
|
|
2695
|
+
async function resolvePortalWidgetSourceConfig(projectDir) {
|
|
2696
|
+
const candidate = path.join(projectDir, WIDGET_CONFIG_PATH);
|
|
2697
|
+
return await fs.pathExists(candidate) ? {
|
|
2698
|
+
path: candidate,
|
|
2699
|
+
relativePath: WIDGET_CONFIG_PATH
|
|
2700
|
+
} : void 0;
|
|
2701
|
+
}
|
|
2702
|
+
async function loadSourceWidgetPackages(projectDir) {
|
|
2703
|
+
const config = await resolvePortalWidgetSourceConfig(projectDir);
|
|
2704
|
+
if (!config) return success([]);
|
|
2705
|
+
let tempDir;
|
|
2706
|
+
try {
|
|
2707
|
+
tempDir = await createTempDirectory(projectDir);
|
|
2708
|
+
const extractFile = path.join(tempDir, SOURCE_PACKAGE_EXTRACT_FILENAME);
|
|
2709
|
+
const outputFile = path.join(tempDir, SOURCE_PACKAGE_OUTPUT_FILENAME);
|
|
2710
|
+
await fs.writeFile(extractFile, createSourcePackageExtractorScript({
|
|
2711
|
+
projectDir,
|
|
2712
|
+
configPath: config.path
|
|
2713
|
+
}), {
|
|
2714
|
+
encoding: "utf-8",
|
|
2715
|
+
flag: "wx"
|
|
2716
|
+
});
|
|
2717
|
+
await execa(process.execPath, [
|
|
2718
|
+
TSX_CLI_PATH,
|
|
2719
|
+
extractFile,
|
|
2720
|
+
outputFile
|
|
2721
|
+
], {
|
|
2722
|
+
cwd: projectDir,
|
|
2723
|
+
stdio: "pipe",
|
|
2724
|
+
env: {
|
|
2725
|
+
...process.env,
|
|
2726
|
+
NODE_ENV: "production"
|
|
2727
|
+
}
|
|
2728
|
+
});
|
|
2729
|
+
const outputResult = await readSourcePackageExtractorOutput(outputFile);
|
|
2730
|
+
if (!outputResult.success) return outputResult;
|
|
2731
|
+
const parsed = outputResult.value;
|
|
2732
|
+
if (parsed.length === 0) return success([]);
|
|
2733
|
+
return success(parsed);
|
|
2734
|
+
} catch (err) {
|
|
2735
|
+
const error = err;
|
|
2736
|
+
return failure({
|
|
2737
|
+
code: "CONFIG_LOAD_FAILED",
|
|
2738
|
+
message: `Failed to load widget packages from ${config.relativePath}`,
|
|
2739
|
+
details: error.stderr ?? error.message ?? String(err)
|
|
2740
|
+
});
|
|
2741
|
+
} finally {
|
|
2742
|
+
if (tempDir) await fs.remove(tempDir).catch(() => {});
|
|
2743
|
+
}
|
|
3019
2744
|
}
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
process.exit(1);
|
|
3029
|
-
}
|
|
3030
|
-
if (!fs.existsSync(path.join(cwd, "src", "portal.config.ts"))) {
|
|
3031
|
-
console.error(chalk.red("Error: No src/portal.config.ts found."));
|
|
3032
|
-
console.error(chalk.yellow("Run this command from a Fluid portal project root."));
|
|
3033
|
-
process.exit(1);
|
|
2745
|
+
async function createTempDirectory(projectDir) {
|
|
2746
|
+
const projectTmpDir = path.join(projectDir, ".fluid", "tmp");
|
|
2747
|
+
try {
|
|
2748
|
+
await fs.ensureDir(projectTmpDir);
|
|
2749
|
+
return await fs.mkdtemp(path.join(projectTmpDir, "widget-packages-"));
|
|
2750
|
+
} catch (err) {
|
|
2751
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2752
|
+
throw new Error(`Unable to create project-local temporary directory at ${projectTmpDir}: ${message}`);
|
|
3034
2753
|
}
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
2754
|
+
}
|
|
2755
|
+
async function readSourcePackageExtractorOutput(outputFile) {
|
|
2756
|
+
let output;
|
|
2757
|
+
try {
|
|
2758
|
+
output = await fs.readFile(outputFile, "utf-8");
|
|
2759
|
+
} catch (err) {
|
|
2760
|
+
return failure({
|
|
2761
|
+
code: "CONFIG_LOAD_FAILED",
|
|
2762
|
+
message: "Widget package extractor did not write an output file",
|
|
2763
|
+
details: err instanceof Error ? err.message : String(err)
|
|
2764
|
+
});
|
|
3038
2765
|
}
|
|
3039
|
-
|
|
3040
|
-
const componentName = toComponentName(widgetType);
|
|
3041
|
-
const displayName = toDisplayName(name);
|
|
3042
|
-
const category = options.category ?? "components";
|
|
2766
|
+
let parsed;
|
|
3043
2767
|
try {
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
2768
|
+
parsed = JSON.parse(output);
|
|
2769
|
+
} catch {
|
|
2770
|
+
return failure({
|
|
2771
|
+
code: "INVALID_FORMAT",
|
|
2772
|
+
message: "Failed to parse widget package output file as JSON",
|
|
2773
|
+
details: `Output was: ${output.slice(0, 200)}`
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
if (!isRecord$4(parsed) || parsed.sentinel !== SOURCE_PACKAGE_OUTPUT_SENTINEL) return failure({
|
|
2777
|
+
code: "INVALID_FORMAT",
|
|
2778
|
+
message: "Widget package extractor output file had an invalid sentinel"
|
|
2779
|
+
});
|
|
2780
|
+
if (!Array.isArray(parsed.data)) return failure({
|
|
2781
|
+
code: "INVALID_FORMAT",
|
|
2782
|
+
message: "Extracted widget package output is not an array",
|
|
2783
|
+
details: `Expected an array, got: ${typeof parsed.data}`
|
|
2784
|
+
});
|
|
2785
|
+
return success(parsed.data);
|
|
3047
2786
|
}
|
|
2787
|
+
function createSourcePackageExtractorScript(options) {
|
|
2788
|
+
return `
|
|
2789
|
+
import fs from "node:fs/promises";
|
|
2790
|
+
import path from "node:path";
|
|
2791
|
+
import { createServer, normalizePath } from "vite";
|
|
3048
2792
|
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
);
|
|
2793
|
+
const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
|
|
2794
|
+
const OUTPUT_SENTINEL = ${JSON.stringify(SOURCE_PACKAGE_OUTPUT_SENTINEL)};
|
|
2795
|
+
const projectRoot = ${JSON.stringify(options.projectDir)};
|
|
2796
|
+
const widgetConfigPath = ${JSON.stringify(options.configPath)};
|
|
2797
|
+
const outputPath = process.argv[2];
|
|
2798
|
+
if (!outputPath) throw new Error("Missing widget package extractor output path.");
|
|
2799
|
+
|
|
2800
|
+
function toViteModuleId(modulePath) {
|
|
2801
|
+
const relativePath = path.relative(projectRoot, modulePath);
|
|
2802
|
+
if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
2803
|
+
return "/" + normalizePath(relativePath);
|
|
2804
|
+
}
|
|
2805
|
+
return normalizePath(modulePath);
|
|
3058
2806
|
}
|
|
3059
|
-
`);
|
|
3060
|
-
await fs.writeFile(path.join(widgetDir, "manifest.ts"), `import type { WidgetManifest } from "@fluid-app/portal-sdk";
|
|
3061
|
-
import { ${componentName} } from "./component";
|
|
3062
2807
|
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
widgetType: "${widgetType}",
|
|
3073
|
-
displayName: "${displayName}",
|
|
3074
|
-
fields: [{ key: "title", label: "Title", type: "text" }],
|
|
2808
|
+
const server = await createServer({
|
|
2809
|
+
root: projectRoot,
|
|
2810
|
+
mode: "production",
|
|
2811
|
+
server: { middlewareMode: true },
|
|
2812
|
+
appType: "custom",
|
|
2813
|
+
logLevel: "error",
|
|
2814
|
+
clearScreen: false,
|
|
2815
|
+
resolve: {
|
|
2816
|
+
conditions: ["module", "browser", "development|production"],
|
|
3075
2817
|
},
|
|
3076
|
-
|
|
3077
|
-
|
|
2818
|
+
ssr: {
|
|
2819
|
+
noExternal: ["@fluid-app/portal-sdk"],
|
|
2820
|
+
resolve: {
|
|
2821
|
+
conditions: ["module", "node", "development|production"],
|
|
2822
|
+
},
|
|
3078
2823
|
},
|
|
3079
|
-
};
|
|
3080
|
-
`);
|
|
3081
|
-
await fs.writeFile(path.join(widgetDir, "index.ts"), `export { ${componentName} } from "./component";
|
|
3082
|
-
export { manifest } from "./manifest";
|
|
3083
|
-
`);
|
|
3084
|
-
} catch (err) {
|
|
3085
|
-
await fs.remove(widgetDir).catch(() => {});
|
|
3086
|
-
console.error(chalk.red("Error: Failed to scaffold widget files."));
|
|
3087
|
-
console.error(err);
|
|
3088
|
-
process.exit(1);
|
|
3089
|
-
}
|
|
3090
|
-
const configPath = path.join(cwd, "src", "portal.config.ts");
|
|
3091
|
-
const camelName = toCamelCase(name);
|
|
3092
|
-
const importLine = `import { manifest as ${camelName} } from "./widgets/${name}";`;
|
|
3093
|
-
const configSource = await fs.readFile(configPath, "utf-8");
|
|
3094
|
-
const withImport = insertImport(configSource, importLine);
|
|
3095
|
-
if (withImport === null) {
|
|
3096
|
-
console.warn(chalk.yellow("Warning: Could not find import statements in portal.config.ts. Add the import manually:"));
|
|
3097
|
-
console.warn(chalk.cyan(` ${importLine}`));
|
|
3098
|
-
}
|
|
3099
|
-
let updated = insertIntoCustomWidgets(withImport ?? configSource, camelName);
|
|
3100
|
-
if (updated === null) if (withImport === null) {
|
|
3101
|
-
console.warn(chalk.yellow("Warning: Could not register widget in portal.config.ts. Add manually:"));
|
|
3102
|
-
console.warn(chalk.cyan(` ${importLine}`));
|
|
3103
|
-
console.warn(chalk.cyan(` customWidgets: [..., ${camelName}]`));
|
|
3104
|
-
} else updated = withImport.trimEnd() + `\n\nexport const customWidgets = [${camelName}];\n`;
|
|
3105
|
-
if (updated !== null) await fs.writeFile(configPath, updated, "utf-8");
|
|
3106
|
-
else if (withImport !== null) await fs.writeFile(configPath, withImport, "utf-8");
|
|
3107
|
-
console.log();
|
|
3108
|
-
console.log(chalk.green("Created widget:") + ` src/widgets/${name}/`);
|
|
3109
|
-
console.log(chalk.gray(" component.tsx — React component"));
|
|
3110
|
-
console.log(chalk.gray(" manifest.ts — WidgetManifest"));
|
|
3111
|
-
console.log(chalk.gray(" index.ts — Re-exports"));
|
|
3112
|
-
console.log();
|
|
3113
|
-
if (updated !== null) {
|
|
3114
|
-
console.log(chalk.green("Registered") + ` in ${chalk.cyan("src/portal.config.ts")}`);
|
|
3115
|
-
console.log();
|
|
3116
|
-
}
|
|
3117
|
-
console.log(chalk.yellow("Next steps:"));
|
|
3118
|
-
console.log(` 1. Edit the component in ${chalk.cyan(`src/widgets/${name}/component.tsx`)}`);
|
|
3119
|
-
console.log(` 2. Customize the manifest fields in ${chalk.cyan(`src/widgets/${name}/manifest.ts`)}`);
|
|
3120
|
-
console.log(` 3. Run ${chalk.cyan("fluid portal dev")} to preview in the builder`);
|
|
3121
2824
|
});
|
|
3122
|
-
|
|
2825
|
+
|
|
2826
|
+
try {
|
|
2827
|
+
const widgetConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
|
|
2828
|
+
|
|
2829
|
+
function isSourceWidgetPackage(value) {
|
|
2830
|
+
return Boolean(value && typeof value === "object" && value[SOURCE_PACKAGE_MARKER] === true);
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
function collectSourceWidgetPackages(mod) {
|
|
2834
|
+
const candidates = [
|
|
2835
|
+
mod.widgetPackage,
|
|
2836
|
+
...(Array.isArray(mod.widgetPackages) ? mod.widgetPackages : []),
|
|
2837
|
+
mod.default,
|
|
2838
|
+
];
|
|
2839
|
+
const byPackageId = new Map();
|
|
2840
|
+
for (const candidate of candidates) {
|
|
2841
|
+
if (!isSourceWidgetPackage(candidate)) continue;
|
|
2842
|
+
if (!byPackageId.has(candidate.packageId)) byPackageId.set(candidate.packageId, candidate);
|
|
2843
|
+
}
|
|
2844
|
+
return Array.from(byPackageId.values());
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
function serializeWidget(widget) {
|
|
2848
|
+
if (!widget || typeof widget !== "object" || Array.isArray(widget)) {
|
|
2849
|
+
return widget;
|
|
2850
|
+
}
|
|
2851
|
+
const { component, ...metadata } = widget;
|
|
2852
|
+
return metadata;
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
function serializePackage(sourcePackage) {
|
|
2856
|
+
return {
|
|
2857
|
+
manifestVersion: sourcePackage.manifestVersion,
|
|
2858
|
+
scope: sourcePackage.scope,
|
|
2859
|
+
packageStableId: sourcePackage.packageStableId,
|
|
2860
|
+
packageId: sourcePackage.packageId,
|
|
2861
|
+
packageType: sourcePackage.packageType,
|
|
2862
|
+
version: sourcePackage.version,
|
|
2863
|
+
cssUrls: sourcePackage.cssUrls,
|
|
2864
|
+
widgets: Array.isArray(sourcePackage.widgets)
|
|
2865
|
+
? sourcePackage.widgets.map(serializeWidget)
|
|
2866
|
+
: sourcePackage.widgets,
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
await fs.writeFile(
|
|
2871
|
+
outputPath,
|
|
2872
|
+
JSON.stringify({
|
|
2873
|
+
sentinel: OUTPUT_SENTINEL,
|
|
2874
|
+
data: collectSourceWidgetPackages(widgetConfig).map(serializePackage),
|
|
2875
|
+
}),
|
|
2876
|
+
"utf-8",
|
|
2877
|
+
);
|
|
2878
|
+
} finally {
|
|
2879
|
+
await server.close();
|
|
2880
|
+
}
|
|
2881
|
+
`;
|
|
2882
|
+
}
|
|
2883
|
+
function isRecord$4(value) {
|
|
2884
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2885
|
+
}
|
|
3123
2886
|
//#endregion
|
|
3124
2887
|
//#region src/utils/widget-package-artifacts.ts
|
|
3125
2888
|
const WIDGET_PACKAGE_BUILDER_VERSION = "1";
|
|
@@ -3240,7 +3003,7 @@ const DEFAULT_MIN_SDK_VERSION = "0.0.0";
|
|
|
3240
3003
|
function validateSingleSourceWidgetPackage(sourcePackages, options = {}) {
|
|
3241
3004
|
if (sourcePackages.length === 0) return validationFailure({
|
|
3242
3005
|
code: "NO_SOURCE_PACKAGE",
|
|
3243
|
-
message: "No defineWidgetPackage source package was found. Export widgetPackage, widgetPackages, or a default widget package from src/widgets.config.ts
|
|
3006
|
+
message: "No defineWidgetPackage source package was found. Export widgetPackage, widgetPackages, or a default widget package from src/widgets.config.ts."
|
|
3244
3007
|
});
|
|
3245
3008
|
if (sourcePackages.length > 1) return validationFailure({
|
|
3246
3009
|
code: "MULTIPLE_SOURCE_PACKAGES",
|
|
@@ -3342,22 +3105,19 @@ function validateSingleSourceWidgetPackage(sourcePackages, options = {}) {
|
|
|
3342
3105
|
errors: []
|
|
3343
3106
|
};
|
|
3344
3107
|
}
|
|
3345
|
-
function
|
|
3346
|
-
const remoteEntryUrl = options.remoteEntryUrl ?? "widget.js";
|
|
3347
|
-
validateRuntimeDescriptorUrl(remoteEntryUrl, "remoteEntryUrl");
|
|
3108
|
+
function buildWidgetPackagePublishManifest(validated, options = {}) {
|
|
3348
3109
|
return {
|
|
3349
3110
|
manifestVersion: WIDGET_PACKAGE_MANIFEST_VERSION,
|
|
3350
|
-
|
|
3351
|
-
|
|
3111
|
+
runtime: "remote-dom",
|
|
3112
|
+
packageId: validated.packageId,
|
|
3113
|
+
source: validated.owner,
|
|
3352
3114
|
version: validated.version,
|
|
3353
|
-
|
|
3115
|
+
workerEntryUrl: toWidgetPackageArtifactPath("widget.js"),
|
|
3354
3116
|
cssUrls: mergeCssUrls(validated.sourcePackage.cssUrls, options.cssUrls),
|
|
3355
|
-
|
|
3117
|
+
assetUrls: [],
|
|
3118
|
+
widgets: validated.widgets.map(({ name: _name, ...widget }) => widget)
|
|
3356
3119
|
};
|
|
3357
3120
|
}
|
|
3358
|
-
function getWidgetPackageDescriptorPackageId(validated) {
|
|
3359
|
-
return validated.owner === "droplet" ? validated.packageKey : validated.packageId;
|
|
3360
|
-
}
|
|
3361
3121
|
function sourceWidgetToDescriptor(widget, type) {
|
|
3362
3122
|
const displayName = widget.displayName ?? widget.name;
|
|
3363
3123
|
return {
|
|
@@ -3371,9 +3131,16 @@ function sourceWidgetToDescriptor(widget, type) {
|
|
|
3371
3131
|
defaultProps: widget.defaultProps ?? {},
|
|
3372
3132
|
container: normalizeContainer(widget.container),
|
|
3373
3133
|
minSdkVersion: widget.minSdkVersion ?? DEFAULT_MIN_SDK_VERSION,
|
|
3374
|
-
resizable: normalizeResizable(widget.resizable)
|
|
3134
|
+
resizable: normalizeResizable(widget.resizable),
|
|
3135
|
+
capabilities: normalizeCapabilities(widget.capabilities ?? [])
|
|
3375
3136
|
};
|
|
3376
3137
|
}
|
|
3138
|
+
function normalizeCapabilities(capabilities) {
|
|
3139
|
+
return capabilities.map((capability) => ({
|
|
3140
|
+
name: capability.name,
|
|
3141
|
+
version: capability.version ?? "1"
|
|
3142
|
+
}));
|
|
3143
|
+
}
|
|
3377
3144
|
function validationFailure(error) {
|
|
3378
3145
|
return {
|
|
3379
3146
|
success: false,
|
|
@@ -3426,8 +3193,9 @@ function validateSourcePackageShape(value) {
|
|
|
3426
3193
|
validateOptionalStringField(widget, "icon", `${widgetPath}.icon`, errors, { requireNonEmpty: true });
|
|
3427
3194
|
validateOptionalStringField(widget, "category", `${widgetPath}.category`, errors, { requireNonEmpty: true });
|
|
3428
3195
|
validateOptionalStringField(widget, "minSdkVersion", `${widgetPath}.minSdkVersion`, errors, { requireNonEmpty: true });
|
|
3429
|
-
|
|
3430
|
-
|
|
3196
|
+
validateOptionalJsonRecordField(widget, "propertySchema", `${widgetPath}.propertySchema`, errors);
|
|
3197
|
+
validateOptionalJsonRecordField(widget, "defaultProps", `${widgetPath}.defaultProps`, errors);
|
|
3198
|
+
validateOptionalCapabilities(widget, "capabilities", `${widgetPath}.capabilities`, errors);
|
|
3431
3199
|
});
|
|
3432
3200
|
return errors;
|
|
3433
3201
|
}
|
|
@@ -3473,85 +3241,67 @@ function validateCssUrl(value, cssUrlPath, errors) {
|
|
|
3473
3241
|
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."
|
|
3474
3242
|
});
|
|
3475
3243
|
}
|
|
3476
|
-
function
|
|
3477
|
-
if (value.trim().length === 0) throw new Error(`${urlPath} must be a non-empty string.`);
|
|
3478
|
-
if (!isValidRuntimeDescriptorUrl(value)) throw new Error(`${urlPath} must be an https URL, trusted local http URL, or relative URL.`);
|
|
3479
|
-
}
|
|
3480
|
-
function isValidRuntimeDescriptorUrl(value) {
|
|
3481
|
-
if (value.trim() !== value || containsUnsafeUrlCharacter(value)) return false;
|
|
3482
|
-
if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.exec(value)) {
|
|
3483
|
-
let parsedUrl;
|
|
3484
|
-
try {
|
|
3485
|
-
parsedUrl = new URL(value);
|
|
3486
|
-
} catch {
|
|
3487
|
-
return false;
|
|
3488
|
-
}
|
|
3489
|
-
return parsedUrl.protocol === "https:" || isTrustedLocalHttpUrl(parsedUrl, value);
|
|
3490
|
-
}
|
|
3491
|
-
if (value.slice(0, 2) === "//") return false;
|
|
3492
|
-
try {
|
|
3493
|
-
new URL(value, "http://localhost");
|
|
3494
|
-
return true;
|
|
3495
|
-
} catch {
|
|
3496
|
-
return false;
|
|
3497
|
-
}
|
|
3498
|
-
}
|
|
3499
|
-
function isTrustedLocalHttpUrl(url, rawUrl) {
|
|
3500
|
-
if (url.protocol !== "http:") return false;
|
|
3501
|
-
const hostname = extractRawHostname(rawUrl).toLowerCase();
|
|
3502
|
-
return hostname === "localhost" || hostname === "[::1]" || hostname === "::1" || isLoopbackIpv4Hostname(hostname);
|
|
3503
|
-
}
|
|
3504
|
-
function extractRawHostname(rawUrl) {
|
|
3505
|
-
const authorityStart = rawUrl.indexOf("://") + 3;
|
|
3506
|
-
const authorityEnd = findAuthorityEnd(rawUrl, authorityStart);
|
|
3507
|
-
const authority = rawUrl.slice(authorityStart, authorityEnd);
|
|
3508
|
-
const hostAndPort = authority.slice(authority.lastIndexOf("@") + 1);
|
|
3509
|
-
if (hostAndPort.charAt(0) === "[") {
|
|
3510
|
-
const bracketEnd = hostAndPort.indexOf("]");
|
|
3511
|
-
return bracketEnd === -1 ? hostAndPort : hostAndPort.slice(0, bracketEnd + 1);
|
|
3512
|
-
}
|
|
3513
|
-
const portStart = hostAndPort.indexOf(":");
|
|
3514
|
-
return portStart === -1 ? hostAndPort : hostAndPort.slice(0, portStart);
|
|
3515
|
-
}
|
|
3516
|
-
function findAuthorityEnd(rawUrl, authorityStart) {
|
|
3517
|
-
let authorityEnd = rawUrl.length;
|
|
3518
|
-
for (const delimiter of [
|
|
3519
|
-
"/",
|
|
3520
|
-
"?",
|
|
3521
|
-
"#"
|
|
3522
|
-
]) {
|
|
3523
|
-
const delimiterIndex = rawUrl.indexOf(delimiter, authorityStart);
|
|
3524
|
-
if (delimiterIndex !== -1 && delimiterIndex < authorityEnd) authorityEnd = delimiterIndex;
|
|
3525
|
-
}
|
|
3526
|
-
return authorityEnd;
|
|
3527
|
-
}
|
|
3528
|
-
function isLoopbackIpv4Hostname(hostname) {
|
|
3529
|
-
const octets = hostname.split(".");
|
|
3530
|
-
if (octets.length !== 4 || octets[0] !== "127") return false;
|
|
3531
|
-
return octets.every(isBoundedIpv4Octet);
|
|
3532
|
-
}
|
|
3533
|
-
function isBoundedIpv4Octet(octet) {
|
|
3534
|
-
if (!/^\d{1,3}$/.test(octet)) return false;
|
|
3535
|
-
if (octet.length > 1 && octet.charAt(0) === "0") return false;
|
|
3536
|
-
const value = Number(octet);
|
|
3537
|
-
return value >= 0 && value <= 255;
|
|
3538
|
-
}
|
|
3539
|
-
function containsUnsafeUrlCharacter(value) {
|
|
3540
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
3541
|
-
const charCode = value.charCodeAt(index);
|
|
3542
|
-
if (charCode <= 31 || charCode === 127 || charCode === 92) return true;
|
|
3543
|
-
}
|
|
3544
|
-
return false;
|
|
3545
|
-
}
|
|
3546
|
-
function validateOptionalRecordField(record, key, path, errors) {
|
|
3244
|
+
function validateOptionalJsonRecordField(record, key, path, errors) {
|
|
3547
3245
|
const value = record[key];
|
|
3548
|
-
if (value === void 0
|
|
3246
|
+
if (value === void 0) return;
|
|
3247
|
+
if (isRecord$3(value) && isJsonValue(value)) return;
|
|
3549
3248
|
errors.push({
|
|
3550
3249
|
code: "INVALID_WIDGET_METADATA",
|
|
3551
3250
|
path,
|
|
3552
3251
|
message: `${path} must be a JSON object when present.`
|
|
3553
3252
|
});
|
|
3554
3253
|
}
|
|
3254
|
+
function validateOptionalCapabilities(record, key, path, errors) {
|
|
3255
|
+
const value = record[key];
|
|
3256
|
+
if (value === void 0) return;
|
|
3257
|
+
if (!Array.isArray(value)) {
|
|
3258
|
+
errors.push({
|
|
3259
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3260
|
+
path,
|
|
3261
|
+
message: `${path} must be an array when present.`
|
|
3262
|
+
});
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
const names = /* @__PURE__ */ new Set();
|
|
3266
|
+
value.forEach((candidate, index) => {
|
|
3267
|
+
const capabilityPath = `${path}[${index}]`;
|
|
3268
|
+
if (!isRecord$3(candidate)) {
|
|
3269
|
+
errors.push({
|
|
3270
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3271
|
+
path: capabilityPath,
|
|
3272
|
+
message: `${capabilityPath} must be a JSON object.`
|
|
3273
|
+
});
|
|
3274
|
+
return;
|
|
3275
|
+
}
|
|
3276
|
+
const name = candidate.name;
|
|
3277
|
+
if (!isCanonicalMetadataString(name)) {
|
|
3278
|
+
errors.push({
|
|
3279
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3280
|
+
path: `${capabilityPath}.name`,
|
|
3281
|
+
message: `${capabilityPath}.name must be a non-empty string without surrounding whitespace or control characters.`
|
|
3282
|
+
});
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
const version = candidate.version;
|
|
3286
|
+
if (version !== void 0 && !isCanonicalMetadataString(version)) errors.push({
|
|
3287
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3288
|
+
path: `${capabilityPath}.version`,
|
|
3289
|
+
message: `${capabilityPath}.version must be a non-empty string without surrounding whitespace or control characters when present.`
|
|
3290
|
+
});
|
|
3291
|
+
if (names.has(name)) {
|
|
3292
|
+
errors.push({
|
|
3293
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3294
|
+
path: `${capabilityPath}.name`,
|
|
3295
|
+
message: `${path} must not contain duplicate capability name ${JSON.stringify(name)}.`
|
|
3296
|
+
});
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
names.add(name);
|
|
3300
|
+
});
|
|
3301
|
+
}
|
|
3302
|
+
function isCanonicalMetadataString(value) {
|
|
3303
|
+
return typeof value === "string" && value.length > 0 && value === value.trim() && !/\p{Cc}/u.test(value);
|
|
3304
|
+
}
|
|
3555
3305
|
function mergeCssUrls(sourceCssUrls, generatedCssUrls) {
|
|
3556
3306
|
const generatedCssUrlSet = new Set(generatedCssUrls ?? []);
|
|
3557
3307
|
generatedCssUrls?.forEach((cssUrl, index) => {
|
|
@@ -3562,7 +3312,10 @@ function mergeCssUrls(sourceCssUrls, generatedCssUrls) {
|
|
|
3562
3312
|
if (generatedCssUrlSet.has(cssUrl)) return;
|
|
3563
3313
|
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.`);
|
|
3564
3314
|
});
|
|
3565
|
-
return Array.from(new Set([...sourceCssUrls ?? [], ...generatedCssUrls ?? []]));
|
|
3315
|
+
return Array.from(new Set([...sourceCssUrls ?? [], ...generatedCssUrls ?? []])).map(toWidgetPackageArtifactPath);
|
|
3316
|
+
}
|
|
3317
|
+
function toWidgetPackageArtifactPath(value) {
|
|
3318
|
+
return value;
|
|
3566
3319
|
}
|
|
3567
3320
|
function validateGeneratedCssArtifactUrl(value, cssUrlPath) {
|
|
3568
3321
|
if (value.trim().length === 0) throw new Error(`${cssUrlPath} must be a non-empty string.`);
|
|
@@ -3639,15 +3392,7 @@ const PROJECT_VITE_CONFIG_CANDIDATES = [
|
|
|
3639
3392
|
"vite.config.js",
|
|
3640
3393
|
"vite.config.mjs"
|
|
3641
3394
|
];
|
|
3642
|
-
const BLOCKED_FLUID_PROJECT_PLUGIN_NAMES = [
|
|
3643
|
-
"fluid-manifest-plugin",
|
|
3644
|
-
"fluid-preview-plugin",
|
|
3645
|
-
"fluid-builder-preview",
|
|
3646
|
-
"fluid-builder-preview-standalone",
|
|
3647
|
-
"fluid-portal-dev",
|
|
3648
|
-
"fluid-backend-dev"
|
|
3649
|
-
];
|
|
3650
|
-
const WIDGET_PACKAGE_QUEUE_GLOBAL = "FluidWidgetPackageQueue";
|
|
3395
|
+
const BLOCKED_FLUID_PROJECT_PLUGIN_NAMES = ["fluid-portal-dev", "fluid-backend-dev"];
|
|
3651
3396
|
const require = createRequire(import.meta.url);
|
|
3652
3397
|
function validateSharedWidgetPackagePublishDir(projectDir, publishDir = DEFAULT_PUBLISH_DIR, options = {}) {
|
|
3653
3398
|
const label = options.optionName ?? "publishDir";
|
|
@@ -3686,9 +3431,11 @@ async function buildSharedWidgetPackage(options) {
|
|
|
3686
3431
|
try {
|
|
3687
3432
|
await validateWidgetPackageOutputPaths(options.projectDir, publishDirInput);
|
|
3688
3433
|
const publishDir = path.resolve(options.projectDir, publishDirInput);
|
|
3434
|
+
const widgetScriptPath = path.join(publishDir, "widget.js");
|
|
3689
3435
|
await fs.emptyDir(publishDir);
|
|
3690
3436
|
const tempDir = path.resolve(options.projectDir, TEMP_BUILD_DIR);
|
|
3691
3437
|
await fs.emptyDir(tempDir);
|
|
3438
|
+
let buildFailure;
|
|
3692
3439
|
try {
|
|
3693
3440
|
await buildWidgetScriptBundle({
|
|
3694
3441
|
projectDir: options.projectDir,
|
|
@@ -3696,15 +3443,25 @@ async function buildSharedWidgetPackage(options) {
|
|
|
3696
3443
|
publishDir,
|
|
3697
3444
|
validated: validation.value
|
|
3698
3445
|
});
|
|
3699
|
-
}
|
|
3700
|
-
|
|
3446
|
+
} catch (error) {
|
|
3447
|
+
buildFailure = { error };
|
|
3448
|
+
}
|
|
3449
|
+
try {
|
|
3450
|
+
await fs.remove(tempDir);
|
|
3451
|
+
} catch {
|
|
3452
|
+
const cleanupFailure = /* @__PURE__ */ new Error("Failed to remove reserved temporary build directory.");
|
|
3453
|
+
if (buildFailure) {
|
|
3454
|
+
const primaryError = buildFailure.error instanceof Error ? buildFailure.error : new Error(String(buildFailure.error));
|
|
3455
|
+
throw new AggregateError([primaryError, cleanupFailure], `${primaryError.message}\n${cleanupFailure.message}`);
|
|
3456
|
+
}
|
|
3457
|
+
throw cleanupFailure;
|
|
3701
3458
|
}
|
|
3459
|
+
if (buildFailure) throw buildFailure.error;
|
|
3702
3460
|
const cssUrls = await collectCssUrls(publishDir);
|
|
3703
|
-
const descriptor =
|
|
3461
|
+
const descriptor = buildWidgetPackagePublishManifest(validation.value, { cssUrls });
|
|
3704
3462
|
const manifestContent = stableStringify(descriptor);
|
|
3705
3463
|
const publishPackageId = descriptor.packageId;
|
|
3706
3464
|
const manifestPath = path.join(publishDir, "manifest.json");
|
|
3707
|
-
const widgetScriptPath = path.join(publishDir, "widget.js");
|
|
3708
3465
|
const publishManifestPath = path.join(publishDir, "publish-manifest.json");
|
|
3709
3466
|
await fs.writeFile(manifestPath, manifestContent, "utf-8");
|
|
3710
3467
|
const artifacts = await collectWidgetPackageArtifacts(publishDir);
|
|
@@ -3719,7 +3476,7 @@ async function buildSharedWidgetPackage(options) {
|
|
|
3719
3476
|
await fs.writeFile(publishManifestPath, stableStringify(publishManifest), "utf-8");
|
|
3720
3477
|
return success({
|
|
3721
3478
|
publishDir,
|
|
3722
|
-
packageId:
|
|
3479
|
+
packageId: validation.value.packageId,
|
|
3723
3480
|
version: validation.value.version,
|
|
3724
3481
|
manifestPath,
|
|
3725
3482
|
publishManifestPath,
|
|
@@ -3736,13 +3493,12 @@ async function buildSharedWidgetPackage(options) {
|
|
|
3736
3493
|
}
|
|
3737
3494
|
}
|
|
3738
3495
|
function createWidgetPackageEntrySource(options) {
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
import {
|
|
3496
|
+
return `import { createElement } from "react";
|
|
3497
|
+
import { startWidgetPackage } from "@fluid-app/portal-sdk/widgets/worker";
|
|
3498
|
+
${options.cssImportPath ? `import ${JSON.stringify(options.cssImportPath)};` : ""}
|
|
3742
3499
|
|
|
3500
|
+
const widgetConfig = await import(${JSON.stringify(options.configImportPath)});
|
|
3743
3501
|
const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
|
|
3744
|
-
const WIDGET_PACKAGE_ID = ${JSON.stringify(runtimePackageId)};
|
|
3745
|
-
const WIDGET_PACKAGE_VERSION = ${JSON.stringify(options.validated.version)};
|
|
3746
3502
|
const descriptors = ${JSON.stringify(options.validated.widgets, null, 2)};
|
|
3747
3503
|
|
|
3748
3504
|
function isSourceWidgetPackage(value) {
|
|
@@ -3759,50 +3515,25 @@ function collectSourceWidgetPackages(mod) {
|
|
|
3759
3515
|
|
|
3760
3516
|
const sourcePackage = collectSourceWidgetPackages(widgetConfig)[0];
|
|
3761
3517
|
if (!sourcePackage) {
|
|
3762
|
-
throw new Error("No Fluid source widget package found while loading widget package bundle.");
|
|
3518
|
+
throw new Error("No Fluid source widget package found while loading widget package worker bundle.");
|
|
3763
3519
|
}
|
|
3764
3520
|
|
|
3765
3521
|
const componentsByName = new Map(
|
|
3766
3522
|
sourcePackage.widgets.map((widget) => [widget.name, widget.component]),
|
|
3767
3523
|
);
|
|
3768
3524
|
|
|
3769
|
-
function createScopedWidgetComponent(component, descriptor) {
|
|
3770
|
-
const wrapperTagName = descriptor.container === "inline" ? "span" : "div";
|
|
3771
|
-
const WrappedFluidWidgetComponent = (props) => createElement(
|
|
3772
|
-
wrapperTagName,
|
|
3773
|
-
{
|
|
3774
|
-
"data-fluid-widget-package": WIDGET_PACKAGE_ID,
|
|
3775
|
-
"data-fluid-widget-version": WIDGET_PACKAGE_VERSION,
|
|
3776
|
-
"data-fluid-widget-name": descriptor.name,
|
|
3777
|
-
},
|
|
3778
|
-
// PortalContainerProvider is not imported here because standalone widget packages
|
|
3779
|
-
// only depend on @fluid-app/portal-sdk, which does not currently expose a
|
|
3780
|
-
// public portal-container provider. Radix/ui-primitives portals remain a
|
|
3781
|
-
// residual isolation risk until a public widget-safe provider is available.
|
|
3782
|
-
createElement(component, props),
|
|
3783
|
-
);
|
|
3784
|
-
WrappedFluidWidgetComponent.displayName = "FluidWidgetPackage(" + WIDGET_PACKAGE_ID + "/" + descriptor.name + ")";
|
|
3785
|
-
return WrappedFluidWidgetComponent;
|
|
3786
|
-
}
|
|
3787
|
-
|
|
3788
3525
|
const widgets = descriptors.map((descriptor) => {
|
|
3789
3526
|
const component = componentsByName.get(descriptor.name);
|
|
3790
|
-
if (typeof component !== "function"
|
|
3791
|
-
throw new Error("Widget package is missing component for " + descriptor.name + ".");
|
|
3527
|
+
if (typeof component !== "function") {
|
|
3528
|
+
throw new Error("Widget package is missing Remote DOM component for " + descriptor.name + ".");
|
|
3792
3529
|
}
|
|
3793
|
-
return {
|
|
3530
|
+
return {
|
|
3531
|
+
...descriptor,
|
|
3532
|
+
component: (props) => createElement(component, props),
|
|
3533
|
+
};
|
|
3794
3534
|
});
|
|
3795
3535
|
|
|
3796
|
-
|
|
3797
|
-
if (!_fluidWidgets || typeof _fluidWidgets.registerPackage !== "function") {
|
|
3798
|
-
throw new Error("FluidWidgets registry is not installed.");
|
|
3799
|
-
}
|
|
3800
|
-
|
|
3801
|
-
_fluidWidgets.registerPackage({
|
|
3802
|
-
packageId: ${JSON.stringify(runtimePackageId)},
|
|
3803
|
-
version: ${JSON.stringify(options.validated.version)},
|
|
3804
|
-
widgets,
|
|
3805
|
-
});
|
|
3536
|
+
startWidgetPackage({ widgets });
|
|
3806
3537
|
`;
|
|
3807
3538
|
}
|
|
3808
3539
|
async function buildWidgetScriptBundle(options) {
|
|
@@ -3810,14 +3541,14 @@ async function buildWidgetScriptBundle(options) {
|
|
|
3810
3541
|
if (!sourceConfig) throw new Error("No portal widget source config found.");
|
|
3811
3542
|
const entryPath = path.join(options.tempDir, "widget-entry.ts");
|
|
3812
3543
|
const viteConfigPath = path.join(options.tempDir, "vite.config.mjs");
|
|
3544
|
+
const rootStylesPath = path.join(options.projectDir, "styles.css");
|
|
3813
3545
|
await fs.writeFile(entryPath, createWidgetPackageEntrySource({
|
|
3814
3546
|
configImportPath: pathToFileURL(sourceConfig.path).href,
|
|
3547
|
+
cssImportPath: await fs.pathExists(rootStylesPath) ? pathToFileURL(rootStylesPath).href : void 0,
|
|
3815
3548
|
validated: options.validated
|
|
3816
3549
|
}), "utf-8");
|
|
3817
3550
|
await fs.writeFile(viteConfigPath, createViteConfigSource({
|
|
3818
3551
|
entryPath,
|
|
3819
|
-
packageId: getWidgetPackageDescriptorPackageId(options.validated),
|
|
3820
|
-
packageVersion: options.validated.version,
|
|
3821
3552
|
publishDir: options.publishDir,
|
|
3822
3553
|
projectConfigPath: await resolveProjectViteConfigPath(options.projectDir)
|
|
3823
3554
|
}), "utf-8");
|
|
@@ -3832,43 +3563,9 @@ async function buildWidgetScriptBundle(options) {
|
|
|
3832
3563
|
stdio: "pipe",
|
|
3833
3564
|
env: {
|
|
3834
3565
|
...process.env,
|
|
3835
|
-
NODE_ENV: "production"
|
|
3836
|
-
NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
|
|
3566
|
+
NODE_ENV: "production"
|
|
3837
3567
|
}
|
|
3838
3568
|
});
|
|
3839
|
-
const widgetScriptPath = path.join(options.publishDir, "widget.js");
|
|
3840
|
-
const widgetScriptContent = await fs.readFile(widgetScriptPath, "utf-8");
|
|
3841
|
-
await fs.writeFile(widgetScriptPath, wrapWidgetPackageScript(widgetScriptContent), "utf-8");
|
|
3842
|
-
}
|
|
3843
|
-
function wrapWidgetPackageScript(widgetScriptContent) {
|
|
3844
|
-
return `;(function () {
|
|
3845
|
-
var globalObject = window;
|
|
3846
|
-
var runWidgetPackage = function fluidWidgetPackageFactory() {
|
|
3847
|
-
// Intentionally shadow Rollup's expected FluidShared global with the wrapper-scoped host globals.
|
|
3848
|
-
var FluidShared = globalObject.FluidShared;
|
|
3849
|
-
if (!FluidShared || !FluidShared.PortalSdk) {
|
|
3850
|
-
throw new Error("window.FluidShared.PortalSdk is not installed.");
|
|
3851
|
-
}
|
|
3852
|
-
${indentScript(widgetScriptContent, " ")}
|
|
3853
|
-
};
|
|
3854
|
-
runWidgetPackage.__fluidRequiresPortalSdk = true;
|
|
3855
|
-
|
|
3856
|
-
if (globalObject.FluidShared && globalObject.FluidShared.PortalSdk) {
|
|
3857
|
-
runWidgetPackage();
|
|
3858
|
-
return;
|
|
3859
|
-
}
|
|
3860
|
-
|
|
3861
|
-
var queue = globalObject.${WIDGET_PACKAGE_QUEUE_GLOBAL};
|
|
3862
|
-
if (!Array.isArray(queue)) {
|
|
3863
|
-
queue = [];
|
|
3864
|
-
globalObject.${WIDGET_PACKAGE_QUEUE_GLOBAL} = queue;
|
|
3865
|
-
}
|
|
3866
|
-
queue.push(runWidgetPackage);
|
|
3867
|
-
})();
|
|
3868
|
-
`;
|
|
3869
|
-
}
|
|
3870
|
-
function indentScript(source, indent) {
|
|
3871
|
-
return source.split("\n").map((line) => line.length > 0 ? `${indent}${line}` : line).join("\n");
|
|
3872
3569
|
}
|
|
3873
3570
|
function resolveViteCliPath(projectDir) {
|
|
3874
3571
|
const projectViteCliPath = resolveViteCliPathFromRequire(createRequire(path.join(projectDir, "package.json")));
|
|
@@ -3896,6 +3593,9 @@ export default defineConfig(${widgetConfigSource});
|
|
|
3896
3593
|
|
|
3897
3594
|
const projectConfigPath = ${JSON.stringify(options.projectConfigPath)};
|
|
3898
3595
|
const widgetConfig = ${widgetConfigSource};
|
|
3596
|
+
const fluidWidgetDefineInvariants = {
|
|
3597
|
+
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
3598
|
+
};
|
|
3899
3599
|
const blockedFluidPluginNames = new Set(${JSON.stringify(BLOCKED_FLUID_PROJECT_PLUGIN_NAMES)});
|
|
3900
3600
|
|
|
3901
3601
|
function isAllowedProjectPlugin(plugin) {
|
|
@@ -3936,30 +3636,35 @@ function sanitizeProjectConfig(config) {
|
|
|
3936
3636
|
return sanitized;
|
|
3937
3637
|
}
|
|
3938
3638
|
|
|
3639
|
+
function copyConfigDefineValue(value) {
|
|
3640
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
3641
|
+
? { ...value }
|
|
3642
|
+
: {};
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
function mergeFluidWidgetDefineInvariants(mergedConfig, safeProjectConfig, widgetConfig) {
|
|
3646
|
+
mergedConfig.define = {
|
|
3647
|
+
...copyConfigDefineValue(safeProjectConfig.define),
|
|
3648
|
+
...copyConfigDefineValue(mergedConfig.define),
|
|
3649
|
+
...copyConfigDefineValue(widgetConfig.define),
|
|
3650
|
+
...fluidWidgetDefineInvariants,
|
|
3651
|
+
};
|
|
3652
|
+
return mergedConfig;
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3939
3655
|
export default defineConfig(async (configEnv) => {
|
|
3940
3656
|
const projectConfig = await loadConfigFromFile(configEnv, projectConfigPath);
|
|
3941
3657
|
const safeProjectConfig = sanitizeProjectConfig(projectConfig?.config ?? {});
|
|
3942
|
-
return mergeConfig(safeProjectConfig, widgetConfig);
|
|
3658
|
+
return mergeFluidWidgetDefineInvariants(mergeConfig(safeProjectConfig, widgetConfig), safeProjectConfig, widgetConfig);
|
|
3943
3659
|
});
|
|
3944
3660
|
`;
|
|
3945
3661
|
}
|
|
3946
3662
|
function createWidgetViteConfigObjectSource(options) {
|
|
3947
3663
|
const configFileSource = options.disableConfigFileLookup ? " configFile: false,\n" : "";
|
|
3948
3664
|
return `(() => {
|
|
3949
|
-
const
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
const widgetPackageGeneratedCssFileName = "widget.css";
|
|
3953
|
-
const widgetPackageKeyframePrefix = "fluid-widget-" + encodeCssIdentifier(widgetPackageId) + "-" + encodeCssIdentifier(widgetPackageVersion) + "-";
|
|
3954
|
-
const widgetPackageLayerPrefix = widgetPackageKeyframePrefix + "layer-";
|
|
3955
|
-
const widgetPackageHostDefaultLayerName = widgetPackageLayerPrefix + "generated-host-defaults";
|
|
3956
|
-
const widgetPackageHostDefaultRule = "@layer " + widgetPackageHostDefaultLayerName + " { " + widgetPackageScopeSelector + " { display: contents; } }";
|
|
3957
|
-
const widgetPackageContainerPrefix = widgetPackageKeyframePrefix + "container-";
|
|
3958
|
-
const widgetPackageCssGlobalNamePrefix = widgetPackageKeyframePrefix + "css-name-";
|
|
3959
|
-
const groupingAtRules = new Set(["media", "supports", "container", "document", "starting-style"]);
|
|
3960
|
-
const unsafeSelectorPattern = /(^|[\\s>+~,])(:root|html|body)(?=$|[\\s.#:[>+~,)])/i;
|
|
3961
|
-
const universalSelectorPattern = /(^|[\\s>+~,])\\*(?=$|[\\s.#:[>+~,)])/g;
|
|
3962
|
-
const pseudoElementOnlySelectorPattern = /^::[A-Za-z-]+(?:\\([^)]*\\))?$/;
|
|
3665
|
+
const fluidWidgetDefineInvariants = {
|
|
3666
|
+
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
3667
|
+
};
|
|
3963
3668
|
|
|
3964
3669
|
function createFluidWidgetBuildInvariants() {
|
|
3965
3670
|
return {
|
|
@@ -3967,26 +3672,18 @@ function createWidgetViteConfigObjectSource(options) {
|
|
|
3967
3672
|
emptyOutDir: false,
|
|
3968
3673
|
outDir: ${JSON.stringify(options.publishDir)},
|
|
3969
3674
|
sourcemap: false,
|
|
3675
|
+
target: "esnext",
|
|
3676
|
+
cssCodeSplit: false,
|
|
3970
3677
|
lib: {
|
|
3971
3678
|
entry: ${JSON.stringify(options.entryPath)},
|
|
3972
|
-
formats: ["
|
|
3973
|
-
name: "FluidWidgetPackage",
|
|
3679
|
+
formats: ["es"],
|
|
3974
3680
|
fileName: () => "widget.js",
|
|
3681
|
+
cssFileName: "widget",
|
|
3975
3682
|
},
|
|
3976
3683
|
rollupOptions: {
|
|
3977
|
-
external: [
|
|
3978
|
-
"react",
|
|
3979
|
-
"react-dom",
|
|
3980
|
-
"react/jsx-runtime",
|
|
3981
|
-
"@fluid-app/portal-sdk",
|
|
3982
|
-
],
|
|
3983
3684
|
output: {
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
"react-dom": "FluidShared.ReactDOM",
|
|
3987
|
-
"react/jsx-runtime": "FluidShared.ReactJsxRuntime",
|
|
3988
|
-
"@fluid-app/portal-sdk": "FluidShared.PortalSdk",
|
|
3989
|
-
},
|
|
3685
|
+
inlineDynamicImports: true,
|
|
3686
|
+
hoistTransitiveImports: false,
|
|
3990
3687
|
},
|
|
3991
3688
|
},
|
|
3992
3689
|
};
|
|
@@ -4000,1179 +3697,26 @@ function createWidgetViteConfigObjectSource(options) {
|
|
|
4000
3697
|
return { build: createFluidWidgetBuildInvariants() };
|
|
4001
3698
|
},
|
|
4002
3699
|
configResolved(config) {
|
|
3700
|
+
config.define = {
|
|
3701
|
+
...(config.define ?? {}),
|
|
3702
|
+
...fluidWidgetDefineInvariants,
|
|
3703
|
+
};
|
|
4003
3704
|
Object.assign(config.build, createFluidWidgetBuildInvariants());
|
|
4004
3705
|
},
|
|
4005
3706
|
};
|
|
4006
3707
|
}
|
|
4007
3708
|
|
|
4008
|
-
function fluidWidgetCssIsolationPlugin() {
|
|
4009
|
-
return {
|
|
4010
|
-
name: "fluid-widget-css-isolation",
|
|
4011
|
-
enforce: "post",
|
|
4012
|
-
generateBundle(_outputOptions, bundle) {
|
|
4013
|
-
let hasCssAsset = false;
|
|
4014
|
-
for (const asset of Object.values(bundle)) {
|
|
4015
|
-
if (!asset || asset.type !== "asset" || !asset.fileName.endsWith(".css")) continue;
|
|
4016
|
-
hasCssAsset = true;
|
|
4017
|
-
const source = typeof asset.source === "string" ? asset.source : new TextDecoder().decode(asset.source);
|
|
4018
|
-
asset.source = isolateWidgetCssAsset(source, asset.fileName);
|
|
4019
|
-
}
|
|
4020
|
-
if (!hasCssAsset) {
|
|
4021
|
-
this.emitFile({
|
|
4022
|
-
type: "asset",
|
|
4023
|
-
fileName: widgetPackageGeneratedCssFileName,
|
|
4024
|
-
source: widgetPackageHostDefaultRule + "\\n",
|
|
4025
|
-
});
|
|
4026
|
-
}
|
|
4027
|
-
},
|
|
4028
|
-
};
|
|
4029
|
-
}
|
|
4030
|
-
|
|
4031
|
-
function isolateWidgetCssAsset(css, assetName) {
|
|
4032
|
-
const keyframeNames = collectKeyframeNames(css);
|
|
4033
|
-
const fontFamilyNames = collectFontFamilyNames(css);
|
|
4034
|
-
const customPropertyNames = collectRegisteredCustomPropertyNames(css);
|
|
4035
|
-
const placementSensitivePrefix = readPlacementSensitiveCssPrefix(css, assetName);
|
|
4036
|
-
const isolatedCss = isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, placementSensitivePrefix.endIndex, css.length).css;
|
|
4037
|
-
return placementSensitivePrefix.css + widgetPackageHostDefaultRule + "\\n" + isolatedCss;
|
|
4038
|
-
}
|
|
4039
|
-
|
|
4040
|
-
function collectKeyframeNames(css) {
|
|
4041
|
-
const names = new Map();
|
|
4042
|
-
visitCssAtRules(css, (atRuleName, atRule) => {
|
|
4043
|
-
if (!isKeyframesAtRule(atRuleName)) return false;
|
|
4044
|
-
const match = /^@(?:-[A-Za-z]+-)?keyframes\\s+([^\\s{]+)/i.exec(atRule.prelude);
|
|
4045
|
-
const name = match?.[1];
|
|
4046
|
-
if (name && !names.has(name)) names.set(name, widgetPackageKeyframePrefix + name);
|
|
4047
|
-
return atRule.blockStart !== -1;
|
|
4048
|
-
});
|
|
4049
|
-
return names;
|
|
4050
|
-
}
|
|
4051
|
-
|
|
4052
|
-
function collectFontFamilyNames(css) {
|
|
4053
|
-
const names = new Map();
|
|
4054
|
-
visitCssAtRules(css, (atRuleName, atRule) => {
|
|
4055
|
-
if (atRuleName !== "font-face" || atRule.blockStart === -1 || atRule.blockEnd === -1) return false;
|
|
4056
|
-
let familyName = "";
|
|
4057
|
-
rewriteDeclarationBlock(css.slice(atRule.blockStart + 1, atRule.blockEnd), (propertyName, rawProperty, value) => {
|
|
4058
|
-
if (propertyName === "font-family" && !familyName) {
|
|
4059
|
-
familyName = normalizeFontFamilyName(value);
|
|
4060
|
-
}
|
|
4061
|
-
return { property: rawProperty, value };
|
|
4062
|
-
});
|
|
4063
|
-
if (familyName && !names.has(familyName)) names.set(familyName, widgetPackageKeyframePrefix + familyName);
|
|
4064
|
-
return true;
|
|
4065
|
-
});
|
|
4066
|
-
return names;
|
|
4067
|
-
}
|
|
4068
|
-
|
|
4069
|
-
function collectRegisteredCustomPropertyNames(css) {
|
|
4070
|
-
const names = new Map();
|
|
4071
|
-
visitCssAtRules(css, (atRuleName, atRule) => {
|
|
4072
|
-
if (atRuleName !== "property") return false;
|
|
4073
|
-
const match = /^@property\\s+(--[A-Za-z0-9_-]+)/i.exec(atRule.prelude);
|
|
4074
|
-
const name = match?.[1];
|
|
4075
|
-
if (name && !names.has(name)) names.set(name, "--" + widgetPackageKeyframePrefix + name.slice(2));
|
|
4076
|
-
return atRule.blockStart !== -1;
|
|
4077
|
-
});
|
|
4078
|
-
return names;
|
|
4079
|
-
}
|
|
4080
|
-
|
|
4081
|
-
function visitCssAtRules(css, visitAtRule) {
|
|
4082
|
-
let index = 0;
|
|
4083
|
-
while (index < css.length) {
|
|
4084
|
-
if (cssStartsWithComment(css, index)) {
|
|
4085
|
-
const commentEnd = css.indexOf("*/", index + 2);
|
|
4086
|
-
index = commentEnd === -1 ? css.length : commentEnd + 2;
|
|
4087
|
-
continue;
|
|
4088
|
-
}
|
|
4089
|
-
const char = css[index];
|
|
4090
|
-
if (char === '"' || char === "'") {
|
|
4091
|
-
index = findCssStringEnd(css, index);
|
|
4092
|
-
continue;
|
|
4093
|
-
}
|
|
4094
|
-
if (char !== "@") {
|
|
4095
|
-
index += 1;
|
|
4096
|
-
continue;
|
|
4097
|
-
}
|
|
4098
|
-
const atRule = readRulePrelude(css, index, css.length);
|
|
4099
|
-
const blockEnd = atRule.blockStart === -1 ? -1 : findMatchingBrace(css, atRule.blockStart, css.length);
|
|
4100
|
-
const shouldSkipBlock = visitAtRule(readAtRuleName(atRule.prelude), { ...atRule, blockEnd });
|
|
4101
|
-
index = shouldSkipBlock && blockEnd !== -1 ? blockEnd + 1 : index + 1;
|
|
4102
|
-
}
|
|
4103
|
-
}
|
|
4104
|
-
|
|
4105
|
-
function isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, startIndex, endIndex) {
|
|
4106
|
-
let output = "";
|
|
4107
|
-
let index = startIndex;
|
|
4108
|
-
while (index < endIndex) {
|
|
4109
|
-
if (css.startsWith("/*", index)) {
|
|
4110
|
-
const commentEnd = css.indexOf("*/", index + 2);
|
|
4111
|
-
const nextIndex = commentEnd === -1 ? endIndex : commentEnd + 2;
|
|
4112
|
-
output += css.slice(index, nextIndex);
|
|
4113
|
-
index = nextIndex;
|
|
4114
|
-
continue;
|
|
4115
|
-
}
|
|
4116
|
-
|
|
4117
|
-
if (/\\s/.test(css[index] ?? "")) {
|
|
4118
|
-
output += css[index];
|
|
4119
|
-
index += 1;
|
|
4120
|
-
continue;
|
|
4121
|
-
}
|
|
4122
|
-
|
|
4123
|
-
if (css[index] === "@") {
|
|
4124
|
-
const atRule = readRulePrelude(css, index, endIndex);
|
|
4125
|
-
const atRuleName = readAtRuleName(atRule.prelude);
|
|
4126
|
-
if (atRule.blockStart === -1) {
|
|
4127
|
-
if (atRuleName === "import") {
|
|
4128
|
-
throw new Error('Unsupported @import rule in CSS asset "' + assetName + '" cannot be isolated safely. Self-host external CSS or fonts via @font-face in widget CSS so the build can isolate them.');
|
|
4129
|
-
}
|
|
4130
|
-
if (atRuleName === "charset" || atRuleName === "namespace") {
|
|
4131
|
-
throw new Error('Unsupported @' + atRuleName + ' rule in CSS asset "' + assetName + '" cannot be isolated safely after generated host defaults. Move @' + atRuleName + ' before all non-@charset/@namespace rules.');
|
|
4132
|
-
}
|
|
4133
|
-
if (atRuleName === "layer") {
|
|
4134
|
-
output += rewriteLayerPrelude(atRule.prelude) + css.slice(index + atRule.prelude.length, atRule.end);
|
|
4135
|
-
index = atRule.end;
|
|
4136
|
-
continue;
|
|
4137
|
-
}
|
|
4138
|
-
output += css.slice(index, atRule.end);
|
|
4139
|
-
index = atRule.end;
|
|
4140
|
-
continue;
|
|
4141
|
-
}
|
|
4142
|
-
|
|
4143
|
-
const blockEnd = findMatchingBrace(css, atRule.blockStart, endIndex);
|
|
4144
|
-
if (blockEnd === -1) {
|
|
4145
|
-
output += css.slice(index, endIndex);
|
|
4146
|
-
index = endIndex;
|
|
4147
|
-
continue;
|
|
4148
|
-
}
|
|
4149
|
-
|
|
4150
|
-
const ruleHeader = css.slice(index, atRule.blockStart + 1);
|
|
4151
|
-
if (isKeyframesAtRule(atRuleName)) {
|
|
4152
|
-
output += rewriteKeyframesHeader(ruleHeader, keyframeNames) + namespaceKeyframesBlock(css.slice(atRule.blockStart + 1, blockEnd), keyframeNames, fontFamilyNames, customPropertyNames, assetName) + "}";
|
|
4153
|
-
} else if (atRuleName === "font-face") {
|
|
4154
|
-
output += ruleHeader + namespaceFontFaceBlock(css.slice(atRule.blockStart + 1, blockEnd), fontFamilyNames, customPropertyNames) + "}";
|
|
4155
|
-
} else if (atRuleName === "property") {
|
|
4156
|
-
output += rewritePropertyHeader(ruleHeader, customPropertyNames) + namespaceCustomPropertyReferences(css.slice(atRule.blockStart + 1, blockEnd), customPropertyNames) + "}";
|
|
4157
|
-
} else if (atRuleName === "layer") {
|
|
4158
|
-
output += rewriteLayerPrelude(atRule.prelude) + css.slice(index + atRule.prelude.length, atRule.blockStart + 1) + isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, atRule.blockStart + 1, blockEnd).css + "}";
|
|
4159
|
-
} else if (groupingAtRules.has(atRuleName)) {
|
|
4160
|
-
const groupingRuleHeader = atRuleName === "container" ? rewriteContainerAtRuleHeader(ruleHeader) : ruleHeader;
|
|
4161
|
-
output += groupingRuleHeader + isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, atRule.blockStart + 1, blockEnd).css + "}";
|
|
4162
|
-
} else {
|
|
4163
|
-
throw new Error('Unsupported @' + atRuleName + ' rule in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
4164
|
-
}
|
|
4165
|
-
index = blockEnd + 1;
|
|
4166
|
-
continue;
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
|
-
const styleRule = readRulePrelude(css, index, endIndex);
|
|
4170
|
-
if (styleRule.blockStart === -1) {
|
|
4171
|
-
output += css.slice(index, styleRule.end);
|
|
4172
|
-
index = styleRule.end;
|
|
4173
|
-
continue;
|
|
4174
|
-
}
|
|
4175
|
-
|
|
4176
|
-
const styleBlockEnd = findMatchingBrace(css, styleRule.blockStart, endIndex);
|
|
4177
|
-
if (styleBlockEnd === -1) {
|
|
4178
|
-
output += css.slice(index, endIndex);
|
|
4179
|
-
index = endIndex;
|
|
4180
|
-
continue;
|
|
4181
|
-
}
|
|
4182
|
-
|
|
4183
|
-
output += isolateSelectorList(styleRule.prelude, assetName) + "{" + namespaceStyleBlock(css.slice(styleRule.blockStart + 1, styleBlockEnd), keyframeNames, fontFamilyNames, customPropertyNames, assetName) + "}";
|
|
4184
|
-
index = styleBlockEnd + 1;
|
|
4185
|
-
}
|
|
4186
|
-
|
|
4187
|
-
return { css: output, index };
|
|
4188
|
-
}
|
|
4189
|
-
|
|
4190
|
-
function readPlacementSensitiveCssPrefix(css, assetName) {
|
|
4191
|
-
let output = "";
|
|
4192
|
-
let index = 0;
|
|
4193
|
-
let hasNamespace = false;
|
|
4194
|
-
while (index < css.length) {
|
|
4195
|
-
const leadingStart = index;
|
|
4196
|
-
index = skipCssWhitespaceAndComments(css, index);
|
|
4197
|
-
const leading = css.slice(leadingStart, index);
|
|
4198
|
-
if (css[index] !== "@") {
|
|
4199
|
-
return { css: output + leading, endIndex: index };
|
|
4200
|
-
}
|
|
4201
|
-
|
|
4202
|
-
const atRule = readRulePrelude(css, index, css.length);
|
|
4203
|
-
const atRuleName = readAtRuleName(atRule.prelude);
|
|
4204
|
-
if (atRuleName !== "charset" && atRuleName !== "namespace") {
|
|
4205
|
-
return { css: output + leading, endIndex: index };
|
|
4206
|
-
}
|
|
4207
|
-
if (atRule.blockStart !== -1) {
|
|
4208
|
-
throw new Error('Unsupported @' + atRuleName + ' rule in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
4209
|
-
}
|
|
4210
|
-
if (atRuleName === "charset" && hasNamespace) {
|
|
4211
|
-
throw new Error('Unsupported @charset rule in CSS asset "' + assetName + '" cannot be isolated safely after @namespace. Move @charset before @namespace.');
|
|
4212
|
-
}
|
|
4213
|
-
if (atRuleName === "namespace") hasNamespace = true;
|
|
4214
|
-
output += leading + css.slice(index, atRule.end);
|
|
4215
|
-
index = atRule.end;
|
|
4216
|
-
}
|
|
4217
|
-
return { css: output, endIndex: index };
|
|
4218
|
-
}
|
|
4219
|
-
|
|
4220
|
-
function readRulePrelude(css, startIndex, endIndex) {
|
|
4221
|
-
let index = startIndex;
|
|
4222
|
-
let quote = "";
|
|
4223
|
-
let bracketDepth = 0;
|
|
4224
|
-
let parenDepth = 0;
|
|
4225
|
-
while (index < endIndex) {
|
|
4226
|
-
const char = css[index];
|
|
4227
|
-
if (quote) {
|
|
4228
|
-
if (char === "\\\\") index += 2;
|
|
4229
|
-
else {
|
|
4230
|
-
if (char === quote) quote = "";
|
|
4231
|
-
index += 1;
|
|
4232
|
-
}
|
|
4233
|
-
continue;
|
|
4234
|
-
}
|
|
4235
|
-
if (cssStartsWithComment(css, index)) {
|
|
4236
|
-
const commentEnd = css.indexOf("*/", index + 2);
|
|
4237
|
-
index = commentEnd === -1 ? endIndex : Math.min(commentEnd + 2, endIndex);
|
|
4238
|
-
continue;
|
|
4239
|
-
}
|
|
4240
|
-
if (char === '"' || char === "'") {
|
|
4241
|
-
quote = char;
|
|
4242
|
-
index += 1;
|
|
4243
|
-
continue;
|
|
4244
|
-
}
|
|
4245
|
-
if (char === "[") bracketDepth += 1;
|
|
4246
|
-
else if (char === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
4247
|
-
else if (char === "(") parenDepth += 1;
|
|
4248
|
-
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
4249
|
-
else if (bracketDepth === 0 && parenDepth === 0 && char === "{") {
|
|
4250
|
-
return { prelude: css.slice(startIndex, index), blockStart: index, end: index + 1 };
|
|
4251
|
-
} else if (bracketDepth === 0 && parenDepth === 0 && char === ";") {
|
|
4252
|
-
return { prelude: css.slice(startIndex, index), blockStart: -1, end: index + 1 };
|
|
4253
|
-
}
|
|
4254
|
-
index += 1;
|
|
4255
|
-
}
|
|
4256
|
-
return { prelude: css.slice(startIndex, endIndex), blockStart: -1, end: endIndex };
|
|
4257
|
-
}
|
|
4258
|
-
|
|
4259
|
-
function findMatchingBrace(css, openBraceIndex, endIndex) {
|
|
4260
|
-
let depth = 1;
|
|
4261
|
-
let index = openBraceIndex + 1;
|
|
4262
|
-
let quote = "";
|
|
4263
|
-
while (index < endIndex) {
|
|
4264
|
-
const char = css[index];
|
|
4265
|
-
if (quote) {
|
|
4266
|
-
if (char === "\\\\") index += 2;
|
|
4267
|
-
else {
|
|
4268
|
-
if (char === quote) quote = "";
|
|
4269
|
-
index += 1;
|
|
4270
|
-
}
|
|
4271
|
-
continue;
|
|
4272
|
-
}
|
|
4273
|
-
if (char === '"' || char === "'") {
|
|
4274
|
-
quote = char;
|
|
4275
|
-
index += 1;
|
|
4276
|
-
continue;
|
|
4277
|
-
}
|
|
4278
|
-
if (css.startsWith("/*", index)) {
|
|
4279
|
-
const commentEnd = css.indexOf("*/", index + 2);
|
|
4280
|
-
index = commentEnd === -1 ? endIndex : commentEnd + 2;
|
|
4281
|
-
continue;
|
|
4282
|
-
}
|
|
4283
|
-
if (char === "{") depth += 1;
|
|
4284
|
-
else if (char === "}") {
|
|
4285
|
-
depth -= 1;
|
|
4286
|
-
if (depth === 0) return index;
|
|
4287
|
-
}
|
|
4288
|
-
index += 1;
|
|
4289
|
-
}
|
|
4290
|
-
return -1;
|
|
4291
|
-
}
|
|
4292
|
-
|
|
4293
|
-
function readAtRuleName(prelude) {
|
|
4294
|
-
const match = /^@([A-Za-z-]+)/.exec(prelude.trim());
|
|
4295
|
-
return match?.[1]?.toLowerCase() ?? "";
|
|
4296
|
-
}
|
|
4297
|
-
|
|
4298
|
-
function isKeyframesAtRule(atRuleName) {
|
|
4299
|
-
return atRuleName === "keyframes" || atRuleName.endsWith("-keyframes");
|
|
4300
|
-
}
|
|
4301
|
-
|
|
4302
|
-
function rewriteKeyframesHeader(header, keyframeNames) {
|
|
4303
|
-
return header.replace(/(@(?:-[A-Za-z]+-)?keyframes\\s+)([^\\s{]+)/, (_match, prefix, name) => prefix + (keyframeNames.get(name) ?? name));
|
|
4304
|
-
}
|
|
4305
|
-
|
|
4306
|
-
function splitSelectorList(selectorList) {
|
|
4307
|
-
const selectors = [];
|
|
4308
|
-
let current = "";
|
|
4309
|
-
let quote = "";
|
|
4310
|
-
let bracketDepth = 0;
|
|
4311
|
-
let parenDepth = 0;
|
|
4312
|
-
for (let index = 0; index < selectorList.length; index += 1) {
|
|
4313
|
-
const char = selectorList[index];
|
|
4314
|
-
if (quote) {
|
|
4315
|
-
current += char;
|
|
4316
|
-
if (char === "\\\\") {
|
|
4317
|
-
index += 1;
|
|
4318
|
-
current += selectorList[index] ?? "";
|
|
4319
|
-
} else if (char === quote) quote = "";
|
|
4320
|
-
continue;
|
|
4321
|
-
}
|
|
4322
|
-
if (char === '"' || char === "'") {
|
|
4323
|
-
quote = char;
|
|
4324
|
-
current += char;
|
|
4325
|
-
continue;
|
|
4326
|
-
}
|
|
4327
|
-
if (cssStartsWithComment(selectorList, index)) {
|
|
4328
|
-
const commentEnd = selectorList.indexOf("*/", index + 2);
|
|
4329
|
-
const nextIndex = commentEnd === -1 ? selectorList.length : commentEnd + 2;
|
|
4330
|
-
current += selectorList.slice(index, nextIndex);
|
|
4331
|
-
index = nextIndex - 1;
|
|
4332
|
-
continue;
|
|
4333
|
-
}
|
|
4334
|
-
if (char === "[") bracketDepth += 1;
|
|
4335
|
-
else if (char === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
4336
|
-
else if (char === "(") parenDepth += 1;
|
|
4337
|
-
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
4338
|
-
if (char === "," && bracketDepth === 0 && parenDepth === 0) {
|
|
4339
|
-
selectors.push(current);
|
|
4340
|
-
current = "";
|
|
4341
|
-
} else current += char;
|
|
4342
|
-
}
|
|
4343
|
-
selectors.push(current);
|
|
4344
|
-
return selectors;
|
|
4345
|
-
}
|
|
4346
|
-
|
|
4347
|
-
function isolateSelectorList(selectorList, assetName) {
|
|
4348
|
-
return splitSelectorList(selectorList).map((selector) => isolateSelector(selector, assetName)).join(",");
|
|
4349
|
-
}
|
|
4350
|
-
|
|
4351
|
-
function isolateSelector(selector, assetName) {
|
|
4352
|
-
const leadingWhitespace = selector.match(/^\\s*/)?.[0] ?? "";
|
|
4353
|
-
const trailingWhitespace = selector.match(/\\s*$/)?.[0] ?? "";
|
|
4354
|
-
const trimmed = selector.trim();
|
|
4355
|
-
assertSafeSelector(trimmed, assetName);
|
|
4356
|
-
if (trimmed.includes(":host")) {
|
|
4357
|
-
return leadingWhitespace + isolateHostSelector(trimmed, assetName) + trailingWhitespace;
|
|
4358
|
-
}
|
|
4359
|
-
return leadingWhitespace + widgetPackageScopeSelector + " " + trimmed + trailingWhitespace;
|
|
4360
|
-
}
|
|
4361
|
-
|
|
4362
|
-
function isolateHostSelector(selector, assetName) {
|
|
4363
|
-
if (!selector.startsWith(":host")) {
|
|
4364
|
-
throw new Error('Unsupported :host selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
4365
|
-
}
|
|
4366
|
-
const match = /^:host(?:\\(([^()]*)\\))?($|[\\s>+~].*)/.exec(selector);
|
|
4367
|
-
if (!match) {
|
|
4368
|
-
throw new Error('Unsupported :host selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
4369
|
-
}
|
|
4370
|
-
const hostSelector = (match[1] ?? "").trim();
|
|
4371
|
-
const selectorAfterHost = match[2] ?? "";
|
|
4372
|
-
const firstSelectorAfterHostIndex = skipCssWhitespaceAndComments(selectorAfterHost, 0);
|
|
4373
|
-
const firstSelectorAfterHostChar = selectorAfterHost[firstSelectorAfterHostIndex] ?? "";
|
|
4374
|
-
if (hostSelector.includes(",") || hasTopLevelHostArgumentCombinator(hostSelector) || firstSelectorAfterHostChar === "+" || firstSelectorAfterHostChar === "~" || (hostSelector && (unsafeSelectorPattern.test(hostSelector) || hasUnsafeUniversalSelector(hostSelector)))) {
|
|
4375
|
-
throw new Error('Unsupported :host selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
4376
|
-
}
|
|
4377
|
-
return widgetPackageScopeSelector + hostSelector + selectorAfterHost;
|
|
4378
|
-
}
|
|
4379
|
-
|
|
4380
|
-
function hasTopLevelHostArgumentCombinator(value) {
|
|
4381
|
-
let quote = "";
|
|
4382
|
-
let bracketDepth = 0;
|
|
4383
|
-
let hasSelectorBeforeWhitespace = false;
|
|
4384
|
-
let index = 0;
|
|
4385
|
-
while (index < value.length) {
|
|
4386
|
-
const char = value[index];
|
|
4387
|
-
if (quote) {
|
|
4388
|
-
if (char === "\\\\") index += 2;
|
|
4389
|
-
else {
|
|
4390
|
-
if (char === quote) quote = "";
|
|
4391
|
-
index += 1;
|
|
4392
|
-
}
|
|
4393
|
-
continue;
|
|
4394
|
-
}
|
|
4395
|
-
if (char === '"' || char === "'") {
|
|
4396
|
-
quote = char;
|
|
4397
|
-
index += 1;
|
|
4398
|
-
continue;
|
|
4399
|
-
}
|
|
4400
|
-
if (char === "[") {
|
|
4401
|
-
bracketDepth += 1;
|
|
4402
|
-
hasSelectorBeforeWhitespace = true;
|
|
4403
|
-
} else if (char === "]") {
|
|
4404
|
-
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
4405
|
-
} else if (bracketDepth === 0 && (char === "+" || char === "~" || char === ">")) {
|
|
4406
|
-
return true;
|
|
4407
|
-
} else if (bracketDepth === 0 && /\\s/.test(char)) {
|
|
4408
|
-
const nextIndex = skipCssWhitespaceAndComments(value, index);
|
|
4409
|
-
if (hasSelectorBeforeWhitespace && nextIndex < value.length) return true;
|
|
4410
|
-
index = nextIndex;
|
|
4411
|
-
continue;
|
|
4412
|
-
} else if (bracketDepth === 0 && cssStartsWithComment(value, index)) {
|
|
4413
|
-
const nextIndex = skipCssWhitespaceAndComments(value, index);
|
|
4414
|
-
if (hasSelectorBeforeWhitespace && nextIndex < value.length) return true;
|
|
4415
|
-
index = nextIndex;
|
|
4416
|
-
continue;
|
|
4417
|
-
} else if (bracketDepth === 0 && !/\\s/.test(char)) {
|
|
4418
|
-
hasSelectorBeforeWhitespace = true;
|
|
4419
|
-
}
|
|
4420
|
-
index += 1;
|
|
4421
|
-
}
|
|
4422
|
-
return false;
|
|
4423
|
-
}
|
|
4424
|
-
|
|
4425
|
-
function assertSafeSelector(selector, assetName) {
|
|
4426
|
-
if (!selector || unsafeSelectorPattern.test(selector) || hasUnsafeUniversalSelector(selector) || pseudoElementOnlySelectorPattern.test(selector)) {
|
|
4427
|
-
throw new Error('Unsafe selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
4428
|
-
}
|
|
4429
|
-
}
|
|
4430
|
-
|
|
4431
|
-
function hasUnsafeUniversalSelector(selector) {
|
|
4432
|
-
universalSelectorPattern.lastIndex = 0;
|
|
4433
|
-
let match;
|
|
4434
|
-
while ((match = universalSelectorPattern.exec(selector)) !== null) {
|
|
4435
|
-
const universalStart = match.index + match[1].length;
|
|
4436
|
-
if (!hasAnchoringSelectorBefore(selector.slice(0, universalStart))) return true;
|
|
4437
|
-
}
|
|
4438
|
-
return false;
|
|
4439
|
-
}
|
|
4440
|
-
|
|
4441
|
-
function hasAnchoringSelectorBefore(value) {
|
|
4442
|
-
return stripCssComments(value).replace(/[\\s>+~]+/g, "").length > 0;
|
|
4443
|
-
}
|
|
4444
|
-
|
|
4445
|
-
function namespaceKeyframesBlock(block, keyframeNames, fontFamilyNames, customPropertyNames, assetName) {
|
|
4446
|
-
let output = "";
|
|
4447
|
-
let index = 0;
|
|
4448
|
-
while (index < block.length) {
|
|
4449
|
-
if (cssStartsWithComment(block, index)) {
|
|
4450
|
-
const commentEnd = block.indexOf("*/", index + 2);
|
|
4451
|
-
const nextIndex = commentEnd === -1 ? block.length : commentEnd + 2;
|
|
4452
|
-
output += block.slice(index, nextIndex);
|
|
4453
|
-
index = nextIndex;
|
|
4454
|
-
continue;
|
|
4455
|
-
}
|
|
4456
|
-
if (/\\s/.test(block[index] ?? "")) {
|
|
4457
|
-
output += block[index];
|
|
4458
|
-
index += 1;
|
|
4459
|
-
continue;
|
|
4460
|
-
}
|
|
4461
|
-
const keyframeRule = readRulePrelude(block, index, block.length);
|
|
4462
|
-
if (keyframeRule.blockStart === -1) {
|
|
4463
|
-
output += block.slice(index, keyframeRule.end);
|
|
4464
|
-
index = keyframeRule.end;
|
|
4465
|
-
continue;
|
|
4466
|
-
}
|
|
4467
|
-
const keyframeBlockEnd = findMatchingBrace(block, keyframeRule.blockStart, block.length);
|
|
4468
|
-
if (keyframeBlockEnd === -1) {
|
|
4469
|
-
output += block.slice(index);
|
|
4470
|
-
index = block.length;
|
|
4471
|
-
continue;
|
|
4472
|
-
}
|
|
4473
|
-
output += keyframeRule.prelude + "{" + namespaceStyleBlock(block.slice(keyframeRule.blockStart + 1, keyframeBlockEnd), keyframeNames, fontFamilyNames, customPropertyNames, assetName) + "}";
|
|
4474
|
-
index = keyframeBlockEnd + 1;
|
|
4475
|
-
}
|
|
4476
|
-
return output;
|
|
4477
|
-
}
|
|
4478
|
-
|
|
4479
|
-
function namespaceStyleBlock(block, keyframeNames, fontFamilyNames, customPropertyNames, assetName) {
|
|
4480
|
-
assertNoNestedCssBlock(block, assetName);
|
|
4481
|
-
return rewriteDeclarationBlock(block, (propertyName, rawProperty, value) => {
|
|
4482
|
-
const nextProperty = namespaceCustomPropertyName(rawProperty, propertyName, customPropertyNames);
|
|
4483
|
-
let nextValue = value;
|
|
4484
|
-
if (propertyName === "animation" || propertyName === "-webkit-animation") {
|
|
4485
|
-
nextValue = namespaceAnimationShorthandValue(nextValue, keyframeNames);
|
|
4486
|
-
} else if (propertyName === "animation-name" || propertyName === "-webkit-animation-name") {
|
|
4487
|
-
nextValue = namespaceAnimationNameValue(nextValue, keyframeNames);
|
|
4488
|
-
} else if (propertyName === "font-family") {
|
|
4489
|
-
nextValue = namespaceFontFamilyValue(nextValue, fontFamilyNames);
|
|
4490
|
-
} else if (propertyName === "font") {
|
|
4491
|
-
nextValue = namespaceFontShorthandValue(nextValue, fontFamilyNames, assetName);
|
|
4492
|
-
}
|
|
4493
|
-
if (propertyName === "container-name") {
|
|
4494
|
-
nextValue = namespaceContainerNameValue(nextValue);
|
|
4495
|
-
} else if (propertyName === "container") {
|
|
4496
|
-
nextValue = namespaceContainerShorthandValue(nextValue);
|
|
4497
|
-
}
|
|
4498
|
-
if (propertyName === "transition" || propertyName === "transition-property" || propertyName === "will-change") {
|
|
4499
|
-
nextValue = namespaceCustomPropertyIdentifierValue(nextValue, customPropertyNames);
|
|
4500
|
-
}
|
|
4501
|
-
if (propertyName === "view-transition-name" || propertyName === "scroll-timeline-name" || propertyName === "animation-timeline") {
|
|
4502
|
-
nextValue = namespaceCssGlobalNameValue(nextValue);
|
|
4503
|
-
}
|
|
4504
|
-
nextValue = namespaceCustomPropertyReferencesValue(nextValue, customPropertyNames);
|
|
4505
|
-
return { property: nextProperty, value: nextValue };
|
|
4506
|
-
});
|
|
4507
|
-
}
|
|
4508
|
-
|
|
4509
|
-
function namespaceFontFaceBlock(block, fontFamilyNames, customPropertyNames) {
|
|
4510
|
-
return rewriteDeclarationBlock(block, (propertyName, rawProperty, value) => {
|
|
4511
|
-
const nextValue = propertyName === "font-family"
|
|
4512
|
-
? namespaceFontFamilyValue(value, fontFamilyNames)
|
|
4513
|
-
: namespaceCustomPropertyReferencesValue(value, customPropertyNames);
|
|
4514
|
-
return { property: rawProperty, value: nextValue };
|
|
4515
|
-
});
|
|
4516
|
-
}
|
|
4517
|
-
|
|
4518
|
-
function namespaceAnimationNameValue(value, keyframeNames) {
|
|
4519
|
-
return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
|
|
4520
|
-
let nextChunk = chunk;
|
|
4521
|
-
for (const [name, scopedName] of keyframeNames) {
|
|
4522
|
-
nextChunk = nextChunk.replace(new RegExp("(^|[^A-Za-z0-9_-])" + escapeRegExp(name) + "(?=$|[^A-Za-z0-9_-])", "g"), (_match, prefix) => prefix + scopedName);
|
|
4523
|
-
}
|
|
4524
|
-
return nextChunk;
|
|
4525
|
-
});
|
|
4526
|
-
}
|
|
4527
|
-
|
|
4528
|
-
function namespaceAnimationShorthandValue(value, keyframeNames) {
|
|
4529
|
-
if (keyframeNames.size === 0) return value;
|
|
4530
|
-
return splitCssCommaList(value).map((item) => rewriteAnimationShorthandItem(item, keyframeNames)).join(",");
|
|
4531
|
-
}
|
|
4532
|
-
|
|
4533
|
-
function rewriteAnimationShorthandItem(item, keyframeNames) {
|
|
4534
|
-
const tokens = readCssValueTokens(item);
|
|
4535
|
-
const reservedState = createAnimationShorthandReservedState();
|
|
4536
|
-
let output = "";
|
|
4537
|
-
let lastIndex = 0;
|
|
4538
|
-
for (const token of tokens) {
|
|
4539
|
-
const scopedName = keyframeNames.get(token.text);
|
|
4540
|
-
const isReservedToken = consumeAnimationShorthandReservedToken(token.text, reservedState);
|
|
4541
|
-
if (!scopedName || isReservedToken) continue;
|
|
4542
|
-
output += item.slice(lastIndex, token.start) + scopedName;
|
|
4543
|
-
lastIndex = token.end;
|
|
4544
|
-
}
|
|
4545
|
-
return output + item.slice(lastIndex);
|
|
4546
|
-
}
|
|
4547
|
-
|
|
4548
|
-
function createAnimationShorthandReservedState() {
|
|
4549
|
-
return {
|
|
4550
|
-
duration: false,
|
|
4551
|
-
delay: false,
|
|
4552
|
-
timingFunction: false,
|
|
4553
|
-
iterationCount: false,
|
|
4554
|
-
direction: false,
|
|
4555
|
-
fillMode: false,
|
|
4556
|
-
playState: false,
|
|
4557
|
-
};
|
|
4558
|
-
}
|
|
4559
|
-
|
|
4560
|
-
function consumeAnimationShorthandReservedToken(token, state) {
|
|
4561
|
-
const lower = token.toLowerCase();
|
|
4562
|
-
if (isAnimationTimeToken(token)) {
|
|
4563
|
-
if (!state.duration) {
|
|
4564
|
-
state.duration = true;
|
|
4565
|
-
return true;
|
|
4566
|
-
}
|
|
4567
|
-
if (!state.delay) {
|
|
4568
|
-
state.delay = true;
|
|
4569
|
-
return true;
|
|
4570
|
-
}
|
|
4571
|
-
return false;
|
|
4572
|
-
}
|
|
4573
|
-
if (!state.timingFunction && isAnimationTimingFunctionToken(lower)) {
|
|
4574
|
-
state.timingFunction = true;
|
|
4575
|
-
return true;
|
|
4576
|
-
}
|
|
4577
|
-
if (!state.iterationCount && isAnimationIterationCountToken(token, lower)) {
|
|
4578
|
-
state.iterationCount = true;
|
|
4579
|
-
return true;
|
|
4580
|
-
}
|
|
4581
|
-
if (!state.direction && animationDirectionKeywords.has(lower)) {
|
|
4582
|
-
state.direction = true;
|
|
4583
|
-
return true;
|
|
4584
|
-
}
|
|
4585
|
-
if (!state.fillMode && animationFillModeKeywords.has(lower)) {
|
|
4586
|
-
state.fillMode = true;
|
|
4587
|
-
return true;
|
|
4588
|
-
}
|
|
4589
|
-
if (!state.playState && animationPlayStateKeywords.has(lower)) {
|
|
4590
|
-
state.playState = true;
|
|
4591
|
-
return true;
|
|
4592
|
-
}
|
|
4593
|
-
if (animationGlobalKeywords.has(lower)) return true;
|
|
4594
|
-
return false;
|
|
4595
|
-
}
|
|
4596
|
-
|
|
4597
|
-
function isAnimationTimeToken(token) {
|
|
4598
|
-
return /^-?(?:\\d+|\\d*\\.\\d+)(?:ms|s)$/i.test(token);
|
|
4599
|
-
}
|
|
4600
|
-
|
|
4601
|
-
function isAnimationTimingFunctionToken(lowerToken) {
|
|
4602
|
-
return animationTimingFunctionKeywords.has(lowerToken) || lowerToken.includes("(");
|
|
4603
|
-
}
|
|
4604
|
-
|
|
4605
|
-
function isAnimationIterationCountToken(token, lowerToken) {
|
|
4606
|
-
return lowerToken === "infinite" || /^-?(?:\\d+|\\d*\\.\\d+)$/.test(token);
|
|
4607
|
-
}
|
|
4608
|
-
|
|
4609
|
-
const animationTimingFunctionKeywords = new Set([
|
|
4610
|
-
"ease",
|
|
4611
|
-
"ease-in",
|
|
4612
|
-
"ease-in-out",
|
|
4613
|
-
"ease-out",
|
|
4614
|
-
"linear",
|
|
4615
|
-
"step-end",
|
|
4616
|
-
"step-start",
|
|
4617
|
-
]);
|
|
4618
|
-
const animationDirectionKeywords = new Set(["normal", "reverse", "alternate", "alternate-reverse"]);
|
|
4619
|
-
const animationFillModeKeywords = new Set(["none", "forwards", "backwards", "both"]);
|
|
4620
|
-
const animationPlayStateKeywords = new Set(["running", "paused"]);
|
|
4621
|
-
const animationGlobalKeywords = new Set(["inherit", "initial", "revert", "revert-layer", "unset"]);
|
|
4622
|
-
|
|
4623
|
-
function namespaceFontFamilyValue(value, fontFamilyNames) {
|
|
4624
|
-
if (fontFamilyNames.size === 0) return value;
|
|
4625
|
-
const important = splitCssImportantSuffix(value);
|
|
4626
|
-
return splitCssCommaList(important.value).map((family) => namespaceSingleFontFamily(family, fontFamilyNames)).join(",") + important.suffix;
|
|
4627
|
-
}
|
|
4628
|
-
|
|
4629
|
-
function namespaceFontShorthandValue(value, fontFamilyNames, assetName) {
|
|
4630
|
-
if (fontFamilyNames.size === 0) return value;
|
|
4631
|
-
const familyStart = findFontShorthandFamilyStart(value);
|
|
4632
|
-
if (familyStart === -1) {
|
|
4633
|
-
if (fontShorthandReferencesScopedFontFamily(value, fontFamilyNames)) {
|
|
4634
|
-
throw new Error('Unsupported font shorthand in CSS asset "' + assetName + '" cannot be isolated safely. Use font-family with font-style/font-weight/font-size longhands, or use a supported font shorthand with an explicit font-size before the family list.');
|
|
4635
|
-
}
|
|
4636
|
-
return value;
|
|
4637
|
-
}
|
|
4638
|
-
return value.slice(0, familyStart) + namespaceFontFamilyValue(value.slice(familyStart), fontFamilyNames);
|
|
4639
|
-
}
|
|
4640
|
-
|
|
4641
|
-
function fontShorthandReferencesScopedFontFamily(value, fontFamilyNames) {
|
|
4642
|
-
const tokens = readCssValueTokens(value);
|
|
4643
|
-
return tokens.some((token) => fontFamilyNames.has(normalizeFontFamilyName(token.text)));
|
|
4644
|
-
}
|
|
4645
|
-
|
|
4646
|
-
function splitCssImportantSuffix(value) {
|
|
4647
|
-
const match = /!\\s*important\\s*$/i.exec(value);
|
|
4648
|
-
if (!match) return { value, suffix: "" };
|
|
4649
|
-
return {
|
|
4650
|
-
value: value.slice(0, match.index),
|
|
4651
|
-
suffix: value.slice(match.index),
|
|
4652
|
-
};
|
|
4653
|
-
}
|
|
4654
|
-
|
|
4655
|
-
function namespaceSingleFontFamily(family, fontFamilyNames) {
|
|
4656
|
-
const leadingWhitespace = family.match(/^\\s*/)?.[0] ?? "";
|
|
4657
|
-
const trailingWhitespace = family.match(/\\s*$/)?.[0] ?? "";
|
|
4658
|
-
const trimmed = family.trim();
|
|
4659
|
-
const normalized = normalizeFontFamilyName(trimmed);
|
|
4660
|
-
const scopedName = fontFamilyNames.get(normalized);
|
|
4661
|
-
if (!scopedName) return family;
|
|
4662
|
-
const quote = trimmed[0] === '"' || trimmed[0] === "'" ? trimmed[0] : "";
|
|
4663
|
-
return leadingWhitespace + (quote ? quote + scopedName + quote : scopedName) + trailingWhitespace;
|
|
4664
|
-
}
|
|
4665
|
-
|
|
4666
|
-
function normalizeFontFamilyName(value) {
|
|
4667
|
-
const trimmed = value.trim();
|
|
4668
|
-
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
4669
|
-
return trimmed.slice(1, -1);
|
|
4670
|
-
}
|
|
4671
|
-
return trimmed;
|
|
4672
|
-
}
|
|
4673
|
-
|
|
4674
|
-
function rewritePropertyHeader(header, customPropertyNames) {
|
|
4675
|
-
let nextHeader = header;
|
|
4676
|
-
for (const [name, scopedName] of customPropertyNames) {
|
|
4677
|
-
nextHeader = nextHeader.replace(new RegExp("(@property\\\\s+)" + escapeRegExp(name) + "(?=$|[\\\\s{])", "g"), (_match, prefix) => prefix + scopedName);
|
|
4678
|
-
}
|
|
4679
|
-
return nextHeader;
|
|
4680
|
-
}
|
|
4681
|
-
|
|
4682
|
-
function rewriteLayerPrelude(prelude) {
|
|
4683
|
-
const match = /^(@layer\\b)([\\s\\S]*)$/i.exec(prelude);
|
|
4684
|
-
if (!match) return prelude;
|
|
4685
|
-
const layerList = match[2] ?? "";
|
|
4686
|
-
if (!layerList.trim()) return prelude;
|
|
4687
|
-
return match[1] + splitCssCommaList(layerList).map(namespaceLayerName).join(",");
|
|
4688
|
-
}
|
|
4689
|
-
|
|
4690
|
-
function namespaceLayerName(layerName) {
|
|
4691
|
-
const leadingWhitespace = layerName.match(/^\\s*/)?.[0] ?? "";
|
|
4692
|
-
const trailingWhitespace = layerName.match(/\\s*$/)?.[0] ?? "";
|
|
4693
|
-
const trimmed = layerName.trim();
|
|
4694
|
-
return trimmed ? leadingWhitespace + widgetPackageLayerPrefix + trimmed + trailingWhitespace : layerName;
|
|
4695
|
-
}
|
|
4696
|
-
|
|
4697
|
-
function rewriteContainerAtRuleHeader(header) {
|
|
4698
|
-
if (!header.endsWith("{")) return header;
|
|
4699
|
-
return rewriteContainerAtRulePrelude(header.slice(0, -1)) + "{";
|
|
4700
|
-
}
|
|
4701
|
-
|
|
4702
|
-
function rewriteContainerAtRulePrelude(prelude) {
|
|
4703
|
-
const match = /^(@container\\b)([\\s\\S]*)$/i.exec(prelude);
|
|
4704
|
-
if (!match) return prelude;
|
|
4705
|
-
const condition = match[2] ?? "";
|
|
4706
|
-
const leadingWhitespace = condition.match(/^\\s*/)?.[0] ?? "";
|
|
4707
|
-
const conditionBody = condition.slice(leadingWhitespace.length);
|
|
4708
|
-
if (!conditionBody || conditionBody.startsWith("(") || /^(?:style|scroll-state)\\s*\\(/.test(conditionBody)) {
|
|
4709
|
-
return prelude;
|
|
4710
|
-
}
|
|
4711
|
-
|
|
4712
|
-
const tokens = readCssValueTokens(conditionBody);
|
|
4713
|
-
const nameToken = tokens[0];
|
|
4714
|
-
if (!nameToken || nameToken.start !== 0 || isContainerQueryConditionKeyword(nameToken.text) || isReservedContainerName(nameToken.text) || nameToken.text.includes("(")) {
|
|
4715
|
-
return prelude;
|
|
4716
|
-
}
|
|
4717
|
-
return match[1] + leadingWhitespace + namespaceContainerName(nameToken.text) + conditionBody.slice(nameToken.end);
|
|
4718
|
-
}
|
|
4719
|
-
|
|
4720
|
-
function namespaceContainerNameValue(value) {
|
|
4721
|
-
return replaceCssValueOutsideProtectedRanges(value, (chunk) => namespaceContainerNamesInChunk(chunk));
|
|
4722
|
-
}
|
|
4723
|
-
|
|
4724
|
-
function namespaceContainerShorthandValue(value) {
|
|
4725
|
-
const slashIndex = findTopLevelSlash(value);
|
|
4726
|
-
if (slashIndex !== -1) {
|
|
4727
|
-
return namespaceContainerNameValue(value.slice(0, slashIndex)) + value.slice(slashIndex);
|
|
4728
|
-
}
|
|
4729
|
-
|
|
4730
|
-
const trimmed = stripCssComments(value).trim().toLowerCase();
|
|
4731
|
-
if (isReservedContainerName(trimmed) || containerTypeKeywords.has(trimmed)) return value;
|
|
4732
|
-
return namespaceContainerNameValue(value);
|
|
4733
|
-
}
|
|
4734
|
-
|
|
4735
|
-
function namespaceContainerNamesInChunk(chunk) {
|
|
4736
|
-
return chunk.replace(/(^|[^A-Za-z0-9_-])([A-Za-z_][A-Za-z0-9_-]*|-[A-Za-z_][A-Za-z0-9_-]*)(?=$|[^A-Za-z0-9_-])/g, (_match, prefix, name) => {
|
|
4737
|
-
if (isReservedContainerName(name)) return prefix + name;
|
|
4738
|
-
return prefix + namespaceContainerName(name);
|
|
4739
|
-
});
|
|
4740
|
-
}
|
|
4741
|
-
|
|
4742
|
-
function namespaceContainerName(name) {
|
|
4743
|
-
return widgetPackageContainerPrefix + name;
|
|
4744
|
-
}
|
|
4745
|
-
|
|
4746
|
-
function isReservedContainerName(name) {
|
|
4747
|
-
return containerNameReservedKeywords.has(name.toLowerCase());
|
|
4748
|
-
}
|
|
4749
|
-
|
|
4750
|
-
function isContainerQueryConditionKeyword(name) {
|
|
4751
|
-
return containerQueryConditionKeywords.has(name.toLowerCase());
|
|
4752
|
-
}
|
|
4753
|
-
|
|
4754
|
-
const containerTypeKeywords = new Set(["normal", "size", "inline-size", "scroll-state"]);
|
|
4755
|
-
const containerQueryConditionKeywords = new Set(["not", "and", "or"]);
|
|
4756
|
-
const containerNameReservedKeywords = new Set(["none", "inherit", "initial", "revert", "revert-layer", "unset"]);
|
|
4757
|
-
|
|
4758
|
-
function namespaceCustomPropertyName(rawProperty, propertyName, customPropertyNames) {
|
|
4759
|
-
const scopedName = customPropertyNames.get(propertyName);
|
|
4760
|
-
if (!scopedName) return rawProperty;
|
|
4761
|
-
return rawProperty.replace(new RegExp(escapeRegExp(propertyName) + "(?=\\\\s*$)"), scopedName);
|
|
4762
|
-
}
|
|
4763
|
-
|
|
4764
|
-
function namespaceCustomPropertyReferences(block, customPropertyNames) {
|
|
4765
|
-
return rewriteDeclarationBlock(block, (propertyName, rawProperty, value) => ({
|
|
4766
|
-
property: rawProperty,
|
|
4767
|
-
value: namespaceCustomPropertyReferencesValue(value, customPropertyNames),
|
|
4768
|
-
}));
|
|
4769
|
-
}
|
|
4770
|
-
|
|
4771
|
-
function namespaceCustomPropertyReferencesValue(value, customPropertyNames) {
|
|
4772
|
-
if (customPropertyNames.size === 0) return value;
|
|
4773
|
-
return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
|
|
4774
|
-
let nextChunk = chunk;
|
|
4775
|
-
for (const [name, scopedName] of customPropertyNames) {
|
|
4776
|
-
nextChunk = nextChunk.replace(new RegExp("(\\\\bvar\\\\(\\\\s*)" + escapeRegExp(name) + "(?=\\\\s*[,)])", "g"), (_match, prefix) => prefix + scopedName);
|
|
4777
|
-
}
|
|
4778
|
-
return nextChunk;
|
|
4779
|
-
});
|
|
4780
|
-
}
|
|
4781
|
-
|
|
4782
|
-
function namespaceCustomPropertyIdentifierValue(value, customPropertyNames) {
|
|
4783
|
-
if (customPropertyNames.size === 0) return value;
|
|
4784
|
-
return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
|
|
4785
|
-
let nextChunk = chunk;
|
|
4786
|
-
for (const [name, scopedName] of customPropertyNames) {
|
|
4787
|
-
nextChunk = nextChunk.replace(new RegExp("(^|[^A-Za-z0-9_-])" + escapeRegExp(name) + "(?=$|[^A-Za-z0-9_-])", "g"), (_match, prefix) => prefix + scopedName);
|
|
4788
|
-
}
|
|
4789
|
-
return nextChunk;
|
|
4790
|
-
});
|
|
4791
|
-
}
|
|
4792
|
-
|
|
4793
|
-
function namespaceCssGlobalNameValue(value) {
|
|
4794
|
-
return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
|
|
4795
|
-
return chunk.replace(/(^|[^A-Za-z0-9_-])(--[A-Za-z0-9_-]+|[A-Za-z_][A-Za-z0-9_-]*|-[A-Za-z_][A-Za-z0-9_-]*)(?=$|[^A-Za-z0-9_-])/g, (_match, prefix, name, offset, source) => {
|
|
4796
|
-
const nextChar = source[offset + prefix.length + name.length] ?? "";
|
|
4797
|
-
const beforeName = source.slice(0, offset + prefix.length);
|
|
4798
|
-
if (nextChar === "(" || /(?:^|[^A-Za-z0-9_-])var\\s*\\($/i.test(beforeName) || cssGlobalNameReservedKeywords.has(name.toLowerCase())) return prefix + name;
|
|
4799
|
-
return prefix + widgetPackageCssGlobalNamePrefix + name;
|
|
4800
|
-
});
|
|
4801
|
-
});
|
|
4802
|
-
}
|
|
4803
|
-
|
|
4804
|
-
const cssGlobalNameReservedKeywords = new Set(["auto", "none", "inherit", "initial", "revert", "revert-layer", "unset"]);
|
|
4805
|
-
|
|
4806
|
-
function assertNoNestedCssBlock(block, assetName) {
|
|
4807
|
-
let index = 0;
|
|
4808
|
-
let quote = "";
|
|
4809
|
-
let parenDepth = 0;
|
|
4810
|
-
while (index < block.length) {
|
|
4811
|
-
const char = block[index];
|
|
4812
|
-
if (quote) {
|
|
4813
|
-
if (char === "\\\\") index += 2;
|
|
4814
|
-
else {
|
|
4815
|
-
if (char === quote) quote = "";
|
|
4816
|
-
index += 1;
|
|
4817
|
-
}
|
|
4818
|
-
continue;
|
|
4819
|
-
}
|
|
4820
|
-
if (cssStartsWithComment(block, index)) {
|
|
4821
|
-
const commentEnd = block.indexOf("*/", index + 2);
|
|
4822
|
-
index = commentEnd === -1 ? block.length : commentEnd + 2;
|
|
4823
|
-
continue;
|
|
4824
|
-
}
|
|
4825
|
-
if (char === '"' || char === "'") {
|
|
4826
|
-
quote = char;
|
|
4827
|
-
index += 1;
|
|
4828
|
-
continue;
|
|
4829
|
-
}
|
|
4830
|
-
if (char === "(") parenDepth += 1;
|
|
4831
|
-
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
4832
|
-
else if (char === "{" && parenDepth === 0) {
|
|
4833
|
-
throw new Error('Unsupported nested CSS rule in CSS asset "' + assetName + '" cannot be isolated safely. Compile nested CSS before building the widget package.');
|
|
4834
|
-
}
|
|
4835
|
-
index += 1;
|
|
4836
|
-
}
|
|
4837
|
-
}
|
|
4838
|
-
|
|
4839
|
-
function rewriteDeclarationBlock(block, rewriteDeclaration) {
|
|
4840
|
-
let output = "";
|
|
4841
|
-
let declarationStart = 0;
|
|
4842
|
-
let index = 0;
|
|
4843
|
-
let quote = "";
|
|
4844
|
-
let parenDepth = 0;
|
|
4845
|
-
while (index < block.length) {
|
|
4846
|
-
const char = block[index];
|
|
4847
|
-
if (quote) {
|
|
4848
|
-
if (char === "\\\\") index += 2;
|
|
4849
|
-
else {
|
|
4850
|
-
if (char === quote) quote = "";
|
|
4851
|
-
index += 1;
|
|
4852
|
-
}
|
|
4853
|
-
continue;
|
|
4854
|
-
}
|
|
4855
|
-
if (cssStartsWithComment(block, index)) {
|
|
4856
|
-
const commentEnd = block.indexOf("*/", index + 2);
|
|
4857
|
-
index = commentEnd === -1 ? block.length : commentEnd + 2;
|
|
4858
|
-
continue;
|
|
4859
|
-
}
|
|
4860
|
-
if (char === '"' || char === "'") {
|
|
4861
|
-
quote = char;
|
|
4862
|
-
index += 1;
|
|
4863
|
-
continue;
|
|
4864
|
-
}
|
|
4865
|
-
if (char === "(") parenDepth += 1;
|
|
4866
|
-
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
4867
|
-
else if (char === ";" && parenDepth === 0) {
|
|
4868
|
-
output += rewriteDeclarationSegment(block.slice(declarationStart, index), rewriteDeclaration) + ";";
|
|
4869
|
-
declarationStart = index + 1;
|
|
4870
|
-
}
|
|
4871
|
-
index += 1;
|
|
4872
|
-
}
|
|
4873
|
-
return output + rewriteDeclarationSegment(block.slice(declarationStart), rewriteDeclaration);
|
|
4874
|
-
}
|
|
4875
|
-
|
|
4876
|
-
function rewriteDeclarationSegment(segment, rewriteDeclaration) {
|
|
4877
|
-
const colonIndex = findDeclarationColon(segment);
|
|
4878
|
-
if (colonIndex === -1) return segment;
|
|
4879
|
-
const rawProperty = segment.slice(0, colonIndex);
|
|
4880
|
-
const rawPropertyName = stripCssComments(rawProperty).trim();
|
|
4881
|
-
const propertyName = rawPropertyName.startsWith("--") ? rawPropertyName : rawPropertyName.toLowerCase();
|
|
4882
|
-
if (!propertyName) return segment;
|
|
4883
|
-
const rewritten = rewriteDeclaration(propertyName, rawProperty, segment.slice(colonIndex + 1));
|
|
4884
|
-
return rewritten.property + ":" + rewritten.value;
|
|
4885
|
-
}
|
|
4886
|
-
|
|
4887
|
-
function findDeclarationColon(segment) {
|
|
4888
|
-
let index = 0;
|
|
4889
|
-
let quote = "";
|
|
4890
|
-
let parenDepth = 0;
|
|
4891
|
-
while (index < segment.length) {
|
|
4892
|
-
const char = segment[index];
|
|
4893
|
-
if (quote) {
|
|
4894
|
-
if (char === "\\\\") index += 2;
|
|
4895
|
-
else {
|
|
4896
|
-
if (char === quote) quote = "";
|
|
4897
|
-
index += 1;
|
|
4898
|
-
}
|
|
4899
|
-
continue;
|
|
4900
|
-
}
|
|
4901
|
-
if (cssStartsWithComment(segment, index)) {
|
|
4902
|
-
const commentEnd = segment.indexOf("*/", index + 2);
|
|
4903
|
-
index = commentEnd === -1 ? segment.length : commentEnd + 2;
|
|
4904
|
-
continue;
|
|
4905
|
-
}
|
|
4906
|
-
if (char === '"' || char === "'") {
|
|
4907
|
-
quote = char;
|
|
4908
|
-
index += 1;
|
|
4909
|
-
continue;
|
|
4910
|
-
}
|
|
4911
|
-
if (char === "(") parenDepth += 1;
|
|
4912
|
-
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
4913
|
-
else if (char === ":" && parenDepth === 0) return index;
|
|
4914
|
-
index += 1;
|
|
4915
|
-
}
|
|
4916
|
-
return -1;
|
|
4917
|
-
}
|
|
4918
|
-
|
|
4919
|
-
function replaceCssValueOutsideProtectedRanges(value, replaceChunk) {
|
|
4920
|
-
let output = "";
|
|
4921
|
-
let chunkStart = 0;
|
|
4922
|
-
let index = 0;
|
|
4923
|
-
while (index < value.length) {
|
|
4924
|
-
if (cssStartsWithComment(value, index)) {
|
|
4925
|
-
output += replaceChunk(value.slice(chunkStart, index));
|
|
4926
|
-
const commentEnd = value.indexOf("*/", index + 2);
|
|
4927
|
-
const nextIndex = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
4928
|
-
output += value.slice(index, nextIndex);
|
|
4929
|
-
index = nextIndex;
|
|
4930
|
-
chunkStart = index;
|
|
4931
|
-
continue;
|
|
4932
|
-
}
|
|
4933
|
-
const char = value[index];
|
|
4934
|
-
if (char === '"' || char === "'") {
|
|
4935
|
-
output += replaceChunk(value.slice(chunkStart, index));
|
|
4936
|
-
const stringEnd = findCssStringEnd(value, index);
|
|
4937
|
-
output += value.slice(index, stringEnd);
|
|
4938
|
-
index = stringEnd;
|
|
4939
|
-
chunkStart = index;
|
|
4940
|
-
continue;
|
|
4941
|
-
}
|
|
4942
|
-
if (isCssUrlFunctionStart(value, index)) {
|
|
4943
|
-
output += replaceChunk(value.slice(chunkStart, index));
|
|
4944
|
-
const urlEnd = findCssFunctionEnd(value, value.indexOf("(", index));
|
|
4945
|
-
output += value.slice(index, urlEnd);
|
|
4946
|
-
index = urlEnd;
|
|
4947
|
-
chunkStart = index;
|
|
4948
|
-
continue;
|
|
4949
|
-
}
|
|
4950
|
-
index += 1;
|
|
4951
|
-
}
|
|
4952
|
-
return output + replaceChunk(value.slice(chunkStart));
|
|
4953
|
-
}
|
|
4954
|
-
|
|
4955
|
-
function findCssStringEnd(value, quoteIndex) {
|
|
4956
|
-
const quote = value[quoteIndex];
|
|
4957
|
-
let index = quoteIndex + 1;
|
|
4958
|
-
while (index < value.length) {
|
|
4959
|
-
const char = value[index];
|
|
4960
|
-
if (char === "\\\\") index += 2;
|
|
4961
|
-
else {
|
|
4962
|
-
index += 1;
|
|
4963
|
-
if (char === quote) return index;
|
|
4964
|
-
}
|
|
4965
|
-
}
|
|
4966
|
-
return value.length;
|
|
4967
|
-
}
|
|
4968
|
-
|
|
4969
|
-
function findCssFunctionEnd(value, openParenIndex) {
|
|
4970
|
-
let index = openParenIndex + 1;
|
|
4971
|
-
let depth = 1;
|
|
4972
|
-
let quote = "";
|
|
4973
|
-
while (index < value.length) {
|
|
4974
|
-
const char = value[index];
|
|
4975
|
-
if (quote) {
|
|
4976
|
-
if (char === "\\\\") index += 2;
|
|
4977
|
-
else {
|
|
4978
|
-
if (char === quote) quote = "";
|
|
4979
|
-
index += 1;
|
|
4980
|
-
}
|
|
4981
|
-
continue;
|
|
4982
|
-
}
|
|
4983
|
-
if (char === '"' || char === "'") {
|
|
4984
|
-
quote = char;
|
|
4985
|
-
index += 1;
|
|
4986
|
-
continue;
|
|
4987
|
-
}
|
|
4988
|
-
if (cssStartsWithComment(value, index)) {
|
|
4989
|
-
const commentEnd = value.indexOf("*/", index + 2);
|
|
4990
|
-
index = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
4991
|
-
continue;
|
|
4992
|
-
}
|
|
4993
|
-
if (char === "(") depth += 1;
|
|
4994
|
-
else if (char === ")") {
|
|
4995
|
-
depth -= 1;
|
|
4996
|
-
if (depth === 0) return index + 1;
|
|
4997
|
-
}
|
|
4998
|
-
index += 1;
|
|
4999
|
-
}
|
|
5000
|
-
return value.length;
|
|
5001
|
-
}
|
|
5002
|
-
|
|
5003
|
-
function findFontShorthandFamilyStart(value) {
|
|
5004
|
-
const tokens = readCssValueTokens(value);
|
|
5005
|
-
for (let index = 0; index < tokens.length; index += 1) {
|
|
5006
|
-
const token = tokens[index];
|
|
5007
|
-
if (!token || !isFontSizeToken(token.text)) continue;
|
|
5008
|
-
if (findTopLevelSlash(token.text) !== -1) return token.end;
|
|
5009
|
-
const slashIndex = skipCssWhitespaceAndComments(value, token.end);
|
|
5010
|
-
if (value[slashIndex] !== "/") return token.end;
|
|
5011
|
-
const lineHeightStart = skipCssWhitespaceAndComments(value, slashIndex + 1);
|
|
5012
|
-
const lineHeightToken = tokens.find((candidate) => candidate.start >= lineHeightStart);
|
|
5013
|
-
return lineHeightToken ? lineHeightToken.end : token.end;
|
|
5014
|
-
}
|
|
5015
|
-
return -1;
|
|
5016
|
-
}
|
|
5017
|
-
|
|
5018
|
-
function readCssValueTokens(value) {
|
|
5019
|
-
const tokens = [];
|
|
5020
|
-
let index = 0;
|
|
5021
|
-
while (index < value.length) {
|
|
5022
|
-
if (/\\s/.test(value[index] ?? "")) {
|
|
5023
|
-
index += 1;
|
|
5024
|
-
continue;
|
|
5025
|
-
}
|
|
5026
|
-
if (cssStartsWithComment(value, index)) {
|
|
5027
|
-
const commentEnd = value.indexOf("*/", index + 2);
|
|
5028
|
-
index = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
5029
|
-
continue;
|
|
5030
|
-
}
|
|
5031
|
-
const start = index;
|
|
5032
|
-
const char = value[index];
|
|
5033
|
-
if (char === '"' || char === "'") {
|
|
5034
|
-
index = findCssStringEnd(value, index);
|
|
5035
|
-
} else {
|
|
5036
|
-
let parenDepth = 0;
|
|
5037
|
-
while (index < value.length) {
|
|
5038
|
-
if (cssStartsWithComment(value, index)) {
|
|
5039
|
-
if (parenDepth === 0) break;
|
|
5040
|
-
const commentEnd = value.indexOf("*/", index + 2);
|
|
5041
|
-
index = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
5042
|
-
continue;
|
|
5043
|
-
}
|
|
5044
|
-
const tokenChar = value[index];
|
|
5045
|
-
if ((tokenChar === '"' || tokenChar === "'") && parenDepth > 0) {
|
|
5046
|
-
index = findCssStringEnd(value, index);
|
|
5047
|
-
continue;
|
|
5048
|
-
}
|
|
5049
|
-
if (tokenChar === "(") parenDepth += 1;
|
|
5050
|
-
else if (tokenChar === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
5051
|
-
if (parenDepth === 0 && /\\s/.test(tokenChar ?? "")) break;
|
|
5052
|
-
index += 1;
|
|
5053
|
-
}
|
|
5054
|
-
}
|
|
5055
|
-
tokens.push({ start, end: index, text: value.slice(start, index) });
|
|
5056
|
-
}
|
|
5057
|
-
return tokens;
|
|
5058
|
-
}
|
|
5059
|
-
|
|
5060
|
-
function isFontSizeToken(token) {
|
|
5061
|
-
const slashIndex = findTopLevelSlash(token);
|
|
5062
|
-
const sizeToken = slashIndex === -1 ? token : token.slice(0, slashIndex);
|
|
5063
|
-
return /^(?:xx-small|x-small|small|medium|large|x-large|xx-large|xxx-large|smaller|larger|math|(?:\\d+|\\d*\\.\\d+)(?:px|em|rem|%|vh|vw|vmin|vmax|ch|ex|cap|ic|lh|rlh|cm|mm|q|in|pc|pt))$/i.test(sizeToken) || isFontSizeFunctionToken(sizeToken);
|
|
5064
|
-
}
|
|
5065
|
-
|
|
5066
|
-
function isFontSizeFunctionToken(token) {
|
|
5067
|
-
return /^(?:calc|clamp|min|max|var|env|abs|sign|round|mod|rem|sin|cos|tan|asin|acos|atan|atan2|pow|sqrt|hypot|log|exp)\\(/i.test(token);
|
|
5068
|
-
}
|
|
5069
|
-
|
|
5070
|
-
function findTopLevelSlash(value) {
|
|
5071
|
-
let index = 0;
|
|
5072
|
-
let quote = "";
|
|
5073
|
-
let parenDepth = 0;
|
|
5074
|
-
while (index < value.length) {
|
|
5075
|
-
const char = value[index];
|
|
5076
|
-
if (quote) {
|
|
5077
|
-
if (char === "\\\\") index += 2;
|
|
5078
|
-
else {
|
|
5079
|
-
if (char === quote) quote = "";
|
|
5080
|
-
index += 1;
|
|
5081
|
-
}
|
|
5082
|
-
continue;
|
|
5083
|
-
}
|
|
5084
|
-
if (char === '"' || char === "'") {
|
|
5085
|
-
quote = char;
|
|
5086
|
-
index += 1;
|
|
5087
|
-
continue;
|
|
5088
|
-
}
|
|
5089
|
-
if (char === "(") parenDepth += 1;
|
|
5090
|
-
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
5091
|
-
else if (char === "/" && parenDepth === 0) return index;
|
|
5092
|
-
index += 1;
|
|
5093
|
-
}
|
|
5094
|
-
return -1;
|
|
5095
|
-
}
|
|
5096
|
-
|
|
5097
|
-
function skipCssWhitespaceAndComments(value, startIndex) {
|
|
5098
|
-
let index = startIndex;
|
|
5099
|
-
while (index < value.length) {
|
|
5100
|
-
if (/\\s/.test(value[index] ?? "")) {
|
|
5101
|
-
index += 1;
|
|
5102
|
-
continue;
|
|
5103
|
-
}
|
|
5104
|
-
if (cssStartsWithComment(value, index)) {
|
|
5105
|
-
const commentEnd = value.indexOf("*/", index + 2);
|
|
5106
|
-
index = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
5107
|
-
continue;
|
|
5108
|
-
}
|
|
5109
|
-
return index;
|
|
5110
|
-
}
|
|
5111
|
-
return index;
|
|
5112
|
-
}
|
|
5113
|
-
|
|
5114
|
-
function cssStartsWithComment(value, index) {
|
|
5115
|
-
return value[index] === "/" && value[index + 1] === "*";
|
|
5116
|
-
}
|
|
5117
|
-
|
|
5118
|
-
function isCssUrlFunctionStart(value, index) {
|
|
5119
|
-
return /^url\\s*\\(/i.test(value.slice(index)) && !/[A-Za-z0-9_-]/.test(value[index - 1] ?? "");
|
|
5120
|
-
}
|
|
5121
|
-
|
|
5122
|
-
function stripCssComments(value) {
|
|
5123
|
-
return value.replace(/\\/\\*[\\s\\S]*?\\*\\//g, "");
|
|
5124
|
-
}
|
|
5125
|
-
|
|
5126
|
-
function splitCssCommaList(value) {
|
|
5127
|
-
const parts = [];
|
|
5128
|
-
let current = "";
|
|
5129
|
-
let quote = "";
|
|
5130
|
-
let parenDepth = 0;
|
|
5131
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
5132
|
-
const char = value[index];
|
|
5133
|
-
if (quote) {
|
|
5134
|
-
current += char;
|
|
5135
|
-
if (char === "\\\\") {
|
|
5136
|
-
index += 1;
|
|
5137
|
-
current += value[index] ?? "";
|
|
5138
|
-
} else if (char === quote) quote = "";
|
|
5139
|
-
continue;
|
|
5140
|
-
}
|
|
5141
|
-
if (char === '"' || char === "'") {
|
|
5142
|
-
quote = char;
|
|
5143
|
-
current += char;
|
|
5144
|
-
continue;
|
|
5145
|
-
}
|
|
5146
|
-
if (char === "(") parenDepth += 1;
|
|
5147
|
-
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
5148
|
-
if (char === "," && parenDepth === 0) {
|
|
5149
|
-
parts.push(current);
|
|
5150
|
-
current = "";
|
|
5151
|
-
} else current += char;
|
|
5152
|
-
}
|
|
5153
|
-
parts.push(current);
|
|
5154
|
-
return parts;
|
|
5155
|
-
}
|
|
5156
|
-
|
|
5157
|
-
function escapeRegExp(value) {
|
|
5158
|
-
return value.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&");
|
|
5159
|
-
}
|
|
5160
|
-
|
|
5161
|
-
function encodeCssIdentifier(value) {
|
|
5162
|
-
return Array.from(value, (char) => char.codePointAt(0)?.toString(16).padStart(2, "0") ?? "00").join("_");
|
|
5163
|
-
}
|
|
5164
|
-
|
|
5165
3709
|
return {
|
|
5166
3710
|
${configFileSource} publicDir: false,
|
|
3711
|
+
define: fluidWidgetDefineInvariants,
|
|
5167
3712
|
resolve: {
|
|
5168
3713
|
conditions: [
|
|
5169
|
-
"fluid-widget-authoring",
|
|
5170
3714
|
"module",
|
|
5171
3715
|
"browser",
|
|
5172
3716
|
"development|production",
|
|
5173
3717
|
],
|
|
5174
3718
|
},
|
|
5175
|
-
plugins: [fluidWidgetBuildInvariantsPlugin()
|
|
3719
|
+
plugins: [fluidWidgetBuildInvariantsPlugin()],
|
|
5176
3720
|
build: createFluidWidgetBuildInvariants(),
|
|
5177
3721
|
};
|
|
5178
3722
|
})()`;
|
|
@@ -5223,12 +3767,22 @@ function stableStringify(value) {
|
|
|
5223
3767
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
5224
3768
|
}
|
|
5225
3769
|
function readCliVersion() {
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
3770
|
+
return resolveCliVersionFromPackageMetadata(path.dirname(fileURLToPath(import.meta.url)), require);
|
|
3771
|
+
}
|
|
3772
|
+
function resolveCliVersionFromPackageMetadata(startDir, loadPackageMetadata) {
|
|
3773
|
+
for (const packageJsonPath of candidatePackageJsonPaths(startDir)) {
|
|
3774
|
+
let packageJson;
|
|
3775
|
+
try {
|
|
3776
|
+
packageJson = loadPackageMetadata(packageJsonPath);
|
|
3777
|
+
} catch (error) {
|
|
3778
|
+
if (isModuleResolutionError(error)) continue;
|
|
3779
|
+
throw new Error("Failed to read Fluid CLI package metadata.", { cause: error });
|
|
3780
|
+
}
|
|
3781
|
+
if (!isRecord$2(packageJson) || packageJson.name !== "@fluid-app/fluid-cli-portal") continue;
|
|
3782
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) throw new Error("Fluid CLI package metadata does not declare a valid version.");
|
|
3783
|
+
return packageJson.version;
|
|
3784
|
+
}
|
|
3785
|
+
throw new Error("Unable to find Fluid CLI package metadata.");
|
|
5232
3786
|
}
|
|
5233
3787
|
function resolveViteCliPathFromRequire(moduleRequire) {
|
|
5234
3788
|
try {
|
|
@@ -5276,22 +3830,6 @@ var WidgetPackageUploadError = class extends Error {
|
|
|
5276
3830
|
this.code = code;
|
|
5277
3831
|
}
|
|
5278
3832
|
};
|
|
5279
|
-
const SESSION_ARRAY_KEYS = [
|
|
5280
|
-
"uploads",
|
|
5281
|
-
"artifacts",
|
|
5282
|
-
"signedUploads",
|
|
5283
|
-
"signed_uploads",
|
|
5284
|
-
"signedUrls",
|
|
5285
|
-
"signed_urls",
|
|
5286
|
-
"uploadUrls",
|
|
5287
|
-
"upload_urls"
|
|
5288
|
-
];
|
|
5289
|
-
const SESSION_RECORD_KEYS = [
|
|
5290
|
-
"signedUrls",
|
|
5291
|
-
"signed_urls",
|
|
5292
|
-
"uploadUrls",
|
|
5293
|
-
"upload_urls"
|
|
5294
|
-
];
|
|
5295
3833
|
function buildWidgetPackageArtifactPayload(artifact) {
|
|
5296
3834
|
return {
|
|
5297
3835
|
path: artifact.path,
|
|
@@ -5333,7 +3871,7 @@ function getWidgetPackageCompleteUploadRoute(owner, version) {
|
|
|
5333
3871
|
async function requestWidgetPackageUploadSession(client, owner, request) {
|
|
5334
3872
|
const payload = buildWidgetPackageUploadSessionPayload(request);
|
|
5335
3873
|
try {
|
|
5336
|
-
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);
|
|
3874
|
+
return normalizeWidgetPackageUploadSession(owner.kind === "company" ? await fluidOs.fluid_os_v0_create_widget_package_version(client, payload) : await client.post(getWidgetPackageUploadSessionRoute(owner), payload), request.version, request.artifacts);
|
|
5337
3875
|
} catch (err) {
|
|
5338
3876
|
if (err instanceof WidgetPackageUploadError) throw err;
|
|
5339
3877
|
throw new WidgetPackageUploadError("SESSION_REQUEST_FAILED", `Failed to create widget package upload session: ${formatUnknownError(err)}`);
|
|
@@ -5372,7 +3910,7 @@ async function uploadWidgetPackageArtifacts(session, artifacts, fetchImpl = fetc
|
|
|
5372
3910
|
async function completeWidgetPackageUpload(client, owner, version, packageKey, artifacts) {
|
|
5373
3911
|
const payload = buildWidgetPackageCompleteUploadPayload(packageKey, artifacts);
|
|
5374
3912
|
try {
|
|
5375
|
-
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);
|
|
3913
|
+
return normalizeWidgetPackageCompleteResponse(owner.kind === "company" ? await fluidOs.fluid_os_v0_complete_widget_package_version_upload(client, version, payload) : await client.post(getWidgetPackageCompleteUploadRoute(owner, version), payload), version);
|
|
5376
3914
|
} catch (err) {
|
|
5377
3915
|
if (err instanceof WidgetPackageUploadError) throw err;
|
|
5378
3916
|
throw new WidgetPackageUploadError("COMPLETE_UPLOAD_FAILED", `Failed to complete widget package upload: ${formatUnknownError(err)}`);
|
|
@@ -5395,128 +3933,53 @@ async function publishWidgetPackageVersion(options) {
|
|
|
5395
3933
|
};
|
|
5396
3934
|
}
|
|
5397
3935
|
function normalizeWidgetPackageUploadSession(response, requestedVersion, requestedArtifacts) {
|
|
5398
|
-
const record = requireRecord(response, "Upload session response must be a JSON object.");
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
const
|
|
5402
|
-
|
|
5403
|
-
const
|
|
5404
|
-
const
|
|
3936
|
+
const record = requireRecord(response, "INVALID_SESSION_RESPONSE", "Upload session response must be a JSON object.");
|
|
3937
|
+
requireFiniteNumberProperty(record, "version_id", "INVALID_SESSION_RESPONSE");
|
|
3938
|
+
requireStringProperty(record, "artifact_base_url", "INVALID_SESSION_RESPONSE");
|
|
3939
|
+
const uploadValues = record["uploads"];
|
|
3940
|
+
if (!Array.isArray(uploadValues)) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", "Upload session response must contain an \"uploads\" array.");
|
|
3941
|
+
const requestedArtifactsByPath = new Map(requestedArtifacts.map((artifact) => [artifact.path, artifact]));
|
|
3942
|
+
const seenUploadPaths = /* @__PURE__ */ new Set();
|
|
3943
|
+
const uploads = uploadValues.map((value, index) => {
|
|
3944
|
+
const upload = requireRecord(value, "INVALID_SESSION_RESPONSE", `Upload session uploads[${index}] must be a JSON object.`);
|
|
3945
|
+
const path = requireStringProperty(upload, "path", "INVALID_SESSION_RESPONSE");
|
|
3946
|
+
const url = requireStringProperty(upload, "upload_url", "INVALID_SESSION_RESPONSE");
|
|
3947
|
+
requireStringProperty(upload, "public_url", "INVALID_SESSION_RESPONSE");
|
|
3948
|
+
const requestedArtifact = requestedArtifactsByPath.get(path);
|
|
3949
|
+
if (!requestedArtifact) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response included unexpected artifact "${path}".`);
|
|
3950
|
+
if (seenUploadPaths.has(path)) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response included duplicate artifact "${path}".`);
|
|
3951
|
+
seenUploadPaths.add(path);
|
|
3952
|
+
return {
|
|
3953
|
+
path,
|
|
3954
|
+
url,
|
|
3955
|
+
contentType: requestedArtifact.contentType,
|
|
3956
|
+
headers: {}
|
|
3957
|
+
};
|
|
3958
|
+
});
|
|
3959
|
+
const missingUploadPaths = requestedArtifacts.map((artifact) => artifact.path).filter((artifactPath) => !seenUploadPaths.has(artifactPath));
|
|
5405
3960
|
if (missingUploadPaths.length > 0) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response is missing signed URLs for artifact(s): ${missingUploadPaths.join(", ")}.`);
|
|
5406
3961
|
return {
|
|
5407
|
-
version,
|
|
3962
|
+
version: requestedVersion,
|
|
5408
3963
|
uploads,
|
|
5409
3964
|
raw: response
|
|
5410
3965
|
};
|
|
5411
3966
|
}
|
|
5412
3967
|
function normalizeWidgetPackageCompleteResponse(response, requestedVersion) {
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
const
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
const packageKey = getStringProperty(nested, "package_key") ?? getStringProperty(nested, "packageKey") ?? getStringProperty(nested, "packageId");
|
|
5422
|
-
const status = getStringProperty(nested, "status") ?? getStringProperty(nested, "state");
|
|
3968
|
+
const record = requireRecord(response, "INVALID_COMPLETE_RESPONSE", "Complete upload response must be a JSON object.");
|
|
3969
|
+
const responseVersion = requireStringProperty(record, "version", "INVALID_COMPLETE_RESPONSE");
|
|
3970
|
+
if (responseVersion !== requestedVersion) throw new WidgetPackageUploadError("INVALID_COMPLETE_RESPONSE", `Complete upload response version "${responseVersion}" did not match requested version "${requestedVersion}".`);
|
|
3971
|
+
const packageKey = requireStringProperty(record, "packageId", "INVALID_COMPLETE_RESPONSE");
|
|
3972
|
+
const status = requireStringProperty(record, "status", "INVALID_COMPLETE_RESPONSE");
|
|
3973
|
+
requireStringProperty(record, "scriptUrl", "INVALID_COMPLETE_RESPONSE");
|
|
3974
|
+
requireStringProperty(record, "manifestUrl", "INVALID_COMPLETE_RESPONSE");
|
|
3975
|
+
requireStringArrayProperty(record, "cssUrls", "INVALID_COMPLETE_RESPONSE");
|
|
5423
3976
|
return {
|
|
5424
|
-
version: responseVersion
|
|
5425
|
-
|
|
5426
|
-
|
|
3977
|
+
version: responseVersion,
|
|
3978
|
+
status,
|
|
3979
|
+
packageKey,
|
|
5427
3980
|
raw: response
|
|
5428
3981
|
};
|
|
5429
3982
|
}
|
|
5430
|
-
function getSignedUploads(records, requestedArtifacts) {
|
|
5431
|
-
const uploads = [];
|
|
5432
|
-
for (const record of records) {
|
|
5433
|
-
for (const key of SESSION_ARRAY_KEYS) {
|
|
5434
|
-
const value = record[key];
|
|
5435
|
-
if (!Array.isArray(value)) continue;
|
|
5436
|
-
for (const item of value) {
|
|
5437
|
-
const upload = signedUploadFromRecord(item, requestedArtifacts);
|
|
5438
|
-
if (upload) uploads.push(upload);
|
|
5439
|
-
}
|
|
5440
|
-
}
|
|
5441
|
-
for (const key of SESSION_RECORD_KEYS) {
|
|
5442
|
-
const value = record[key];
|
|
5443
|
-
if (!isRecord$1(value) || Array.isArray(value)) continue;
|
|
5444
|
-
for (const [artifactPath, uploadValue] of Object.entries(value)) {
|
|
5445
|
-
const upload = signedUploadFromKeyedValue(artifactPath, uploadValue, requestedArtifacts);
|
|
5446
|
-
if (upload) uploads.push(upload);
|
|
5447
|
-
}
|
|
5448
|
-
}
|
|
5449
|
-
}
|
|
5450
|
-
return dedupeUploads(uploads);
|
|
5451
|
-
}
|
|
5452
|
-
function signedUploadFromRecord(value, requestedArtifacts) {
|
|
5453
|
-
if (!isRecord$1(value)) return null;
|
|
5454
|
-
const artifactPath = readFirstString(value, [
|
|
5455
|
-
"path",
|
|
5456
|
-
"key",
|
|
5457
|
-
"artifactPath",
|
|
5458
|
-
"artifact_path",
|
|
5459
|
-
"artifactKey",
|
|
5460
|
-
"artifact_key",
|
|
5461
|
-
"name"
|
|
5462
|
-
]);
|
|
5463
|
-
const url = readFirstString(value, [
|
|
5464
|
-
"url",
|
|
5465
|
-
"signedUrl",
|
|
5466
|
-
"signed_url",
|
|
5467
|
-
"uploadUrl",
|
|
5468
|
-
"upload_url"
|
|
5469
|
-
]);
|
|
5470
|
-
if (!artifactPath || !url) return null;
|
|
5471
|
-
const requestedArtifact = findRequestedArtifact(requestedArtifacts, artifactPath);
|
|
5472
|
-
const contentType = readFirstString(value, [
|
|
5473
|
-
"contentType",
|
|
5474
|
-
"content_type",
|
|
5475
|
-
"expectedContentType",
|
|
5476
|
-
"expected_content_type"
|
|
5477
|
-
]) ?? requestedArtifact?.contentType;
|
|
5478
|
-
if (!contentType) return null;
|
|
5479
|
-
return {
|
|
5480
|
-
path: artifactPath,
|
|
5481
|
-
url,
|
|
5482
|
-
contentType,
|
|
5483
|
-
headers: getHeaders(value["headers"])
|
|
5484
|
-
};
|
|
5485
|
-
}
|
|
5486
|
-
function signedUploadFromKeyedValue(artifactPath, value, requestedArtifacts) {
|
|
5487
|
-
const requestedArtifact = findRequestedArtifact(requestedArtifacts, artifactPath);
|
|
5488
|
-
if (typeof value === "string") {
|
|
5489
|
-
if (!requestedArtifact) return null;
|
|
5490
|
-
return {
|
|
5491
|
-
path: artifactPath,
|
|
5492
|
-
url: value,
|
|
5493
|
-
contentType: requestedArtifact.contentType,
|
|
5494
|
-
headers: {}
|
|
5495
|
-
};
|
|
5496
|
-
}
|
|
5497
|
-
if (!isRecord$1(value)) return null;
|
|
5498
|
-
const url = readFirstString(value, [
|
|
5499
|
-
"url",
|
|
5500
|
-
"signedUrl",
|
|
5501
|
-
"signed_url",
|
|
5502
|
-
"uploadUrl",
|
|
5503
|
-
"upload_url"
|
|
5504
|
-
]);
|
|
5505
|
-
if (!url) return null;
|
|
5506
|
-
const contentType = readFirstString(value, [
|
|
5507
|
-
"contentType",
|
|
5508
|
-
"content_type",
|
|
5509
|
-
"expectedContentType",
|
|
5510
|
-
"expected_content_type"
|
|
5511
|
-
]) ?? requestedArtifact?.contentType;
|
|
5512
|
-
if (!contentType) return null;
|
|
5513
|
-
return {
|
|
5514
|
-
path: artifactPath,
|
|
5515
|
-
url,
|
|
5516
|
-
contentType,
|
|
5517
|
-
headers: getHeaders(value["headers"])
|
|
5518
|
-
};
|
|
5519
|
-
}
|
|
5520
3983
|
function normalizeHeaderName(name) {
|
|
5521
3984
|
return name.toLowerCase();
|
|
5522
3985
|
}
|
|
@@ -5529,57 +3992,47 @@ function mergeUploadHeaders(headers, contentType) {
|
|
|
5529
3992
|
merged["Content-Type"] = contentType;
|
|
5530
3993
|
return merged;
|
|
5531
3994
|
}
|
|
5532
|
-
function
|
|
5533
|
-
|
|
5534
|
-
for (const upload of uploads) deduped.set(upload.path, upload);
|
|
5535
|
-
return Array.from(deduped.values());
|
|
5536
|
-
}
|
|
5537
|
-
function findRequestedArtifact(requestedArtifacts, artifactPath) {
|
|
5538
|
-
return requestedArtifacts.find((artifact) => artifact.path === artifactPath);
|
|
5539
|
-
}
|
|
5540
|
-
function requireRecord(value, message) {
|
|
5541
|
-
if (!isRecord$1(value) || Array.isArray(value)) throw new WidgetPackageUploadError("INVALID_RESPONSE", message);
|
|
3995
|
+
function requireRecord(value, code, message) {
|
|
3996
|
+
if (!isRecord$1(value) || Array.isArray(value)) throw new WidgetPackageUploadError(code, message);
|
|
5542
3997
|
return value;
|
|
5543
3998
|
}
|
|
5544
3999
|
function isRecord$1(value) {
|
|
5545
4000
|
return typeof value === "object" && value !== null;
|
|
5546
4001
|
}
|
|
5547
|
-
function
|
|
4002
|
+
function requireStringProperty(record, key, code) {
|
|
5548
4003
|
const value = record[key];
|
|
5549
4004
|
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
5550
|
-
|
|
5551
|
-
}
|
|
5552
|
-
function
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
if (
|
|
5560
|
-
|
|
5561
|
-
for (const [key, headerValue] of Object.entries(value)) if (typeof headerValue === "string") headers[key] = headerValue;
|
|
5562
|
-
return headers;
|
|
5563
|
-
}
|
|
5564
|
-
function getResponseVersion(record) {
|
|
5565
|
-
const directVersion = getStringProperty(record, "version");
|
|
5566
|
-
if (directVersion) return directVersion;
|
|
5567
|
-
const nested = getNestedVersionRecord(record);
|
|
5568
|
-
return nested ? getStringProperty(nested, "version") : void 0;
|
|
5569
|
-
}
|
|
5570
|
-
function getNestedVersionRecord(record) {
|
|
5571
|
-
const candidates = [
|
|
5572
|
-
record["portal_widget_package_version"],
|
|
5573
|
-
record["widget_package_version"],
|
|
5574
|
-
record["package_version"]
|
|
5575
|
-
];
|
|
5576
|
-
for (const candidate of candidates) if (isRecord$1(candidate) && !Array.isArray(candidate)) return candidate;
|
|
4005
|
+
throw new WidgetPackageUploadError(code, `Response field "${key}" must be a non-empty string.`);
|
|
4006
|
+
}
|
|
4007
|
+
function requireFiniteNumberProperty(record, key, code) {
|
|
4008
|
+
const value = record[key];
|
|
4009
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4010
|
+
throw new WidgetPackageUploadError(code, `Response field "${key}" must be a finite number.`);
|
|
4011
|
+
}
|
|
4012
|
+
function requireStringArrayProperty(record, key, code) {
|
|
4013
|
+
const value = record[key];
|
|
4014
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.trim().length > 0)) return value;
|
|
4015
|
+
throw new WidgetPackageUploadError(code, `Response field "${key}" must be an array of non-empty strings.`);
|
|
5577
4016
|
}
|
|
5578
4017
|
function formatUnknownError(err) {
|
|
5579
4018
|
const base = err instanceof Error ? err.message : String(err);
|
|
5580
|
-
if (isRecord$1(err) && "data" in err) return `${base} — ${
|
|
4019
|
+
if (isRecord$1(err) && "data" in err) return `${base} — ${safeStringify(err.data)}`;
|
|
5581
4020
|
return base;
|
|
5582
4021
|
}
|
|
4022
|
+
function safeStringify(value) {
|
|
4023
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
4024
|
+
try {
|
|
4025
|
+
return JSON.stringify(value, (_key, item) => {
|
|
4026
|
+
if (typeof item === "bigint") return `${item}n`;
|
|
4027
|
+
if (typeof item !== "object" || item === null) return item;
|
|
4028
|
+
if (seen.has(item)) return "[Circular]";
|
|
4029
|
+
seen.add(item);
|
|
4030
|
+
return item;
|
|
4031
|
+
}) ?? String(value);
|
|
4032
|
+
} catch {
|
|
4033
|
+
return "[unserializable error data]";
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
5583
4036
|
//#endregion
|
|
5584
4037
|
//#region src/commands/widget-package-publish.ts
|
|
5585
4038
|
async function publishWidgetRuntimeArtifacts(options, dependencies = {}) {
|
|
@@ -6074,7 +4527,7 @@ const createVersionCommand = new Command("create").description("Create a new ver
|
|
|
6074
4527
|
console.log(chalk.bold("Creating version..."));
|
|
6075
4528
|
let result;
|
|
6076
4529
|
try {
|
|
6077
|
-
result = await fluid_os_v0_create_fluid_osversion(client, definitionId);
|
|
4530
|
+
result = await fluidOs.fluid_os_v0_create_fluid_osversion(client, definitionId);
|
|
6078
4531
|
} catch (err) {
|
|
6079
4532
|
console.error(chalk.red("Error:") + " Failed to create version — " + (err instanceof Error ? err.message : String(err)));
|
|
6080
4533
|
process.exit(1);
|
|
@@ -6092,7 +4545,7 @@ const createVersionCommand = new Command("create").description("Create a new ver
|
|
|
6092
4545
|
if (options.activate) {
|
|
6093
4546
|
console.log("Activating version...");
|
|
6094
4547
|
try {
|
|
6095
|
-
await fluid_os_v0_update_fluid_osversion(client, definitionId, version.id, { version: { active: true } });
|
|
4548
|
+
await fluidOs.fluid_os_v0_update_fluid_osversion(client, definitionId, version.id, { version: { active: true } });
|
|
6096
4549
|
} catch (err) {
|
|
6097
4550
|
console.error(chalk.red("Error:") + " Failed to activate version — " + (err instanceof Error ? err.message : String(err)));
|
|
6098
4551
|
process.exit(1);
|
|
@@ -6107,7 +4560,7 @@ const listVersionCommand = new Command("list").description("List all versions of
|
|
|
6107
4560
|
const definitionId = await requireDefinitionId();
|
|
6108
4561
|
let result;
|
|
6109
4562
|
try {
|
|
6110
|
-
result = await fluid_os_v0_list_fluid_osversions(client, definitionId);
|
|
4563
|
+
result = await fluidOs.fluid_os_v0_list_fluid_osversions(client, definitionId);
|
|
6111
4564
|
} catch (err) {
|
|
6112
4565
|
console.error(chalk.red("Error:") + " Failed to list versions — " + (err instanceof Error ? err.message : String(err)));
|
|
6113
4566
|
process.exit(1);
|
|
@@ -6150,7 +4603,7 @@ const activateVersionCommand = new Command("activate").description("Activate a s
|
|
|
6150
4603
|
console.log();
|
|
6151
4604
|
console.log(chalk.bold("Activating version..."));
|
|
6152
4605
|
try {
|
|
6153
|
-
await fluid_os_v0_update_fluid_osversion(client, definitionId, versionId, { version: { active: true } });
|
|
4606
|
+
await fluidOs.fluid_os_v0_update_fluid_osversion(client, definitionId, versionId, { version: { active: true } });
|
|
6154
4607
|
} catch (err) {
|
|
6155
4608
|
console.error(chalk.red("Error:") + " Failed to activate version — " + (err instanceof Error ? err.message : String(err)));
|
|
6156
4609
|
process.exit(1);
|
|
@@ -6233,6 +4686,6 @@ const plugin = {
|
|
|
6233
4686
|
}
|
|
6234
4687
|
};
|
|
6235
4688
|
//#endregion
|
|
6236
|
-
export { CANONICAL_PROJECT_NAME, FILE_SYSTEM_ERRORS, GitError, TEMPLATES, TEMPLATE_REPO_URL, WIDGET_PACKAGE_BUILDER_VERSION, WidgetPackageUploadError, buildIdToSlugMap, buildNavigationIdToSlugMap, buildSharedWidgetPackage, buildSnapshot, buildThemeIdToSlugMap, buildWidgetPackageCompleteUploadPayload,
|
|
4689
|
+
export { CANONICAL_PROJECT_NAME, FILE_SYSTEM_ERRORS, GitError, TEMPLATES, TEMPLATE_REPO_URL, WIDGET_PACKAGE_BUILDER_VERSION, WidgetPackageUploadError, buildIdToSlugMap, buildNavigationIdToSlugMap, buildSharedWidgetPackage, buildSnapshot, buildThemeIdToSlugMap, buildWidgetPackageCompleteUploadPayload, buildWidgetPackagePublishManifest, buildWidgetPackageUploadSessionPayload, categorizeChanges, cloneCommand, collectWidgetPackageArtifacts, completeWidgetPackageUpload, computeArtifactMetadata, computeFileHash, copyTemplate, copyTemplateSafe, createAuthenticatedWidgetPackageClient, createCommand, createDirectory, createDirectorySafe, createPortalGitCredential, createPublishManifestMetadata, createWidgetPackageEntrySource, createWidgetPackagePublishRequest, plugin as default, deployCommand, deriveScreenSlug, deriveSlug, diffAgainstSnapshot, directoryExists, doctorCommand, ensureGitAvailable, fileExists, getArtifactContentType, getInstallCommand, getOriginUrl, getRunCommand, getSdkVersion, getSdkVersionSafe, getTemplatePaths, getWidgetPackageCompleteUploadRoute, getWidgetPackageUploadSessionRoute, gitAheadBehindVsFetchHead, gitAutoCommit, gitClone, gitFetch, gitInit, gitMergeFastForward, gitMissingError, gitPush, hasUncommittedChanges, installDependencies, isGitAvailable, isGitRepo, loadSourceWidgetPackages, normalizeWidgetPackageCompleteResponse, normalizeWidgetPackageUploadSession, pathExists, portalGitCredentialPath, printDryRunSessionPayload, printRuntimeOnlyNotice, printWidgetPublishSummary, promptProjectConfig, publishWidgetPackageVersion, publishWidgetRuntimeArtifacts, pullCommand, pushCommand, readFileSafe, readMappings, readSnapshot, removeMapping, requestWidgetPackageUploadSession, resolveIdToSlug, resolvePortalWidgetSourceConfig, resolveSlugToId, rewriteProjectName, runPackageManager, runPortalGitSync, scaffoldFromGitHub, sha256, slugFromPath, topLevelWidgetCommand, transformNavigation, transformNavigationItems, transformProfile, transformScreen, transformTheme, updateMapping, uploadWidgetPackageArtifacts, validateCrossReferences, validateSingleSourceWidgetPackage, validateWidgetPublishOutDir, versionCommand, widgetCommand, writeFileSafe, writeFluidrcProfile, writeMappings, writeSnapshot };
|
|
6237
4690
|
|
|
6238
4691
|
//# sourceMappingURL=index.mjs.map
|