@fluid-app/fluid-cli-portal 0.1.41 → 0.1.43
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 +48 -414
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +744 -2281
- 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
|
|
@@ -699,11 +700,14 @@ async function gitRemoteBranchExists(cwd, remoteUrl, branch = "main") {
|
|
|
699
700
|
/**
|
|
700
701
|
* Adopt `FETCH_HEAD` as the current (unborn) branch's base: point the
|
|
701
702
|
* branch ref at it and load its tree into the index, leaving the working
|
|
702
|
-
* tree untouched. The next auto-commit
|
|
703
|
-
* tree
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
703
|
+
* tree untouched. The next auto-commit (run with `ignoreRemovals`) then
|
|
704
|
+
* records the local project tree on top of the remote history — local
|
|
705
|
+
* additions/edits win, and remote-only files survive because a
|
|
706
|
+
* content-only local dir (e.g. populated by `fluid portal pull`) is no
|
|
707
|
+
* statement about what the full repo tracks — so the push fast-forwards
|
|
708
|
+
* instead of being rejected for unrelated history. Only call when
|
|
709
|
+
* `hasCommits` is false; with local commits it would silently rewrite
|
|
710
|
+
* the branch ref.
|
|
707
711
|
* Mirrors the widget CLI's first-push adoption (the backend provisions
|
|
708
712
|
* repos with an auto-init/template commit, so a fresh root commit is
|
|
709
713
|
* always unrelated history).
|
|
@@ -780,7 +784,9 @@ async function gitMergeFastForward(cwd) {
|
|
|
780
784
|
}
|
|
781
785
|
}
|
|
782
786
|
/**
|
|
783
|
-
* `git add -A && git commit -m <message
|
|
787
|
+
* `git add -A && git commit -m <message>` (or, with
|
|
788
|
+
* `{ ignoreRemovals: true }`, `git add --ignore-removal .` — see
|
|
789
|
+
* GitAutoCommitOptions).
|
|
784
790
|
*
|
|
785
791
|
* `git add -A` intentionally stages the whole work tree: a portal's
|
|
786
792
|
* provisioned repo tracks the entire project (it's seeded from the
|
|
@@ -794,9 +800,14 @@ async function gitMergeFastForward(cwd) {
|
|
|
794
800
|
* identity so the commit isn't rejected. "nothing to commit" is treated
|
|
795
801
|
* as a no-op, not an error.
|
|
796
802
|
*/
|
|
797
|
-
async function gitAutoCommit(cwd, message) {
|
|
803
|
+
async function gitAutoCommit(cwd, message, options = {}) {
|
|
804
|
+
const addArgs = options.ignoreRemovals ? [
|
|
805
|
+
"add",
|
|
806
|
+
"--ignore-removal",
|
|
807
|
+
"."
|
|
808
|
+
] : ["add", "-A"];
|
|
798
809
|
try {
|
|
799
|
-
await execa("git",
|
|
810
|
+
await execa("git", addArgs, { cwd });
|
|
800
811
|
} catch (err) {
|
|
801
812
|
throw toGitError(err, "add");
|
|
802
813
|
}
|
|
@@ -819,7 +830,7 @@ async function gitAutoCommit(cwd, message) {
|
|
|
819
830
|
cwd,
|
|
820
831
|
stdio: [
|
|
821
832
|
"ignore",
|
|
822
|
-
"
|
|
833
|
+
"pipe",
|
|
823
834
|
"pipe"
|
|
824
835
|
]
|
|
825
836
|
});
|
|
@@ -828,7 +839,7 @@ async function gitAutoCommit(cwd, message) {
|
|
|
828
839
|
if (missing) throw missing;
|
|
829
840
|
const e = err;
|
|
830
841
|
const streams = `${typeof e.stdout === "string" ? e.stdout : ""}\n${typeof e.stderr === "string" ? e.stderr : ""}`;
|
|
831
|
-
if (/nothing to commit/i.test(streams)) return;
|
|
842
|
+
if (/nothing to commit|no changes added to commit/i.test(streams)) return;
|
|
832
843
|
throw toGitError(err, "commit");
|
|
833
844
|
}
|
|
834
845
|
}
|
|
@@ -1149,7 +1160,7 @@ async function autoPull(cwd) {
|
|
|
1149
1160
|
console.log(chalk.yellow("No portal/ directory found.") + " Attempting to pull content...");
|
|
1150
1161
|
console.log();
|
|
1151
1162
|
try {
|
|
1152
|
-
const { pullCommand } = await import("./pull-
|
|
1163
|
+
const { pullCommand } = await import("./pull-Cl7omGLf.mjs").then((n) => n.n);
|
|
1153
1164
|
await pullCommand.parseAsync([], { from: "user" });
|
|
1154
1165
|
return hasPortalContent(cwd);
|
|
1155
1166
|
} catch (err) {
|
|
@@ -1204,615 +1215,173 @@ const devCommand = new Command("dev").description("Start the development server
|
|
|
1204
1215
|
}
|
|
1205
1216
|
});
|
|
1206
1217
|
//#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
|
-
};
|
|
1218
|
+
//#region src/commands/build.ts
|
|
1219
|
+
const buildCommand = new Command("build").description("Build the application for production").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
|
|
1220
|
+
const cwd = process.cwd();
|
|
1221
|
+
if (!existsSync(join(cwd, "package.json"))) {
|
|
1222
|
+
console.error(chalk.red("Error: No package.json found in current directory"));
|
|
1223
|
+
console.error(chalk.yellow("Make sure you're in a Fluid project directory"));
|
|
1224
|
+
process.exit(1);
|
|
1232
1225
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1226
|
+
if (!existsSync(join(cwd, "vite.config.ts"))) {
|
|
1227
|
+
console.error(chalk.red("Error: No vite.config.ts found"));
|
|
1228
|
+
console.error(chalk.yellow("This command must be run from a Fluid project directory"));
|
|
1229
|
+
process.exit(1);
|
|
1230
|
+
}
|
|
1231
|
+
console.log();
|
|
1232
|
+
console.log(chalk.bold("Building for production..."));
|
|
1233
|
+
console.log();
|
|
1234
|
+
const spinner = ora("Building...").start();
|
|
1238
1235
|
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)
|
|
1236
|
+
await execa("pnpm", ["run", "build"], {
|
|
1237
|
+
cwd,
|
|
1238
|
+
stdio: "pipe"
|
|
1273
1239
|
});
|
|
1274
|
-
|
|
1275
|
-
|
|
1240
|
+
spinner.succeed("Build completed");
|
|
1241
|
+
const outDir = options.outDir ?? "dist";
|
|
1242
|
+
console.log();
|
|
1243
|
+
console.log(`Output written to ${chalk.cyan(outDir)}/`);
|
|
1244
|
+
console.log();
|
|
1245
|
+
console.log("To preview the build locally:");
|
|
1246
|
+
console.log(chalk.cyan(" pnpm vite preview"));
|
|
1247
|
+
console.log();
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
spinner.fail("Build failed");
|
|
1250
|
+
const execaError = error;
|
|
1251
|
+
if (execaError.stderr) console.error(execaError.stderr);
|
|
1252
|
+
process.exit(1);
|
|
1276
1253
|
}
|
|
1254
|
+
});
|
|
1255
|
+
//#endregion
|
|
1256
|
+
//#region src/utils/push-validation.ts
|
|
1257
|
+
/**
|
|
1258
|
+
* Cross-reference validation and change categorization utilities for the push command.
|
|
1259
|
+
*
|
|
1260
|
+
* Extracted into a standalone utility so that pure logic can be tested
|
|
1261
|
+
* without pulling in CLI dependencies (ora, chalk, prompts, etc.).
|
|
1262
|
+
*/
|
|
1263
|
+
/**
|
|
1264
|
+
* Extract the slug from a file path (e.g., "screens/home.json" -> "home").
|
|
1265
|
+
*/
|
|
1266
|
+
function slugFromPath(filePath) {
|
|
1267
|
+
return basename(filePath, ".json");
|
|
1277
1268
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1269
|
+
/**
|
|
1270
|
+
* Extract the resource type directory from a file path (e.g., "screens/home.json" -> "screens").
|
|
1271
|
+
*/
|
|
1272
|
+
function resourceTypeFromPath(filePath) {
|
|
1273
|
+
const dir = filePath.split("/")[0];
|
|
1274
|
+
if (dir === "screens" || dir === "themes" || dir === "navigations" || dir === "profiles") return dir;
|
|
1275
|
+
return null;
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Read and parse a JSON file from the portal directory.
|
|
1279
|
+
*/
|
|
1280
|
+
async function readPortalFile$1(portalDir, relativePath) {
|
|
1281
|
+
const content = await readFile(join(portalDir, relativePath), "utf-8");
|
|
1282
|
+
return JSON.parse(content);
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Categorize a snapshot diff into resource-type-specific change lists.
|
|
1286
|
+
*/
|
|
1287
|
+
function categorizeChanges(diff) {
|
|
1288
|
+
const result = {
|
|
1289
|
+
screens: {
|
|
1290
|
+
new: [],
|
|
1291
|
+
changed: [],
|
|
1292
|
+
deleted: []
|
|
1293
|
+
},
|
|
1294
|
+
themes: {
|
|
1295
|
+
new: [],
|
|
1296
|
+
changed: [],
|
|
1297
|
+
deleted: []
|
|
1298
|
+
},
|
|
1299
|
+
navigations: {
|
|
1300
|
+
new: [],
|
|
1301
|
+
changed: [],
|
|
1302
|
+
deleted: []
|
|
1303
|
+
},
|
|
1304
|
+
profiles: {
|
|
1305
|
+
new: [],
|
|
1306
|
+
changed: [],
|
|
1307
|
+
deleted: []
|
|
1308
|
+
}
|
|
1309
|
+
};
|
|
1310
|
+
for (const file of diff.new) {
|
|
1311
|
+
const type = resourceTypeFromPath(file);
|
|
1312
|
+
if (type) result[type].new.push(file);
|
|
1313
|
+
}
|
|
1314
|
+
for (const file of diff.changed) {
|
|
1315
|
+
const type = resourceTypeFromPath(file);
|
|
1316
|
+
if (type) result[type].changed.push(file);
|
|
1317
|
+
}
|
|
1318
|
+
for (const file of diff.deleted) {
|
|
1319
|
+
const type = resourceTypeFromPath(file);
|
|
1320
|
+
if (type) result[type].deleted.push(file);
|
|
1286
1321
|
}
|
|
1322
|
+
return result;
|
|
1287
1323
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1324
|
+
/**
|
|
1325
|
+
* Validate that all cross-references between local portal files are valid.
|
|
1326
|
+
*
|
|
1327
|
+
* Checks:
|
|
1328
|
+
* - Navigation items' "screen" slugs reference existing screen files or mappings
|
|
1329
|
+
* - Profile "navigation" and "mobile_navigation" slugs reference existing nav files or mappings
|
|
1330
|
+
* - Profile "themes" slugs reference existing theme files or mappings
|
|
1331
|
+
*/
|
|
1332
|
+
async function validateCrossReferences(portalDir, mappings, changes) {
|
|
1333
|
+
const errors = [];
|
|
1334
|
+
const validScreenSlugs = buildValidSlugsSet(portalDir, "screens", mappings);
|
|
1335
|
+
const validNavSlugs = buildValidSlugsSet(portalDir, "navigations", mappings);
|
|
1336
|
+
const validThemeSlugs = buildValidSlugsSet(portalDir, "themes", mappings);
|
|
1337
|
+
for (const file of changes.screens.deleted) validScreenSlugs.delete(slugFromPath(file));
|
|
1338
|
+
for (const file of changes.navigations.deleted) validNavSlugs.delete(slugFromPath(file));
|
|
1339
|
+
for (const file of changes.themes.deleted) validThemeSlugs.delete(slugFromPath(file));
|
|
1340
|
+
const navFilesToCheck = [...changes.navigations.new, ...changes.navigations.changed];
|
|
1341
|
+
for (const file of navFilesToCheck) try {
|
|
1342
|
+
validateNavigationItems((await readPortalFile$1(portalDir, file)).navigation_items, file, validScreenSlugs, errors);
|
|
1343
|
+
} catch {
|
|
1344
|
+
errors.push({
|
|
1345
|
+
file,
|
|
1346
|
+
message: "Failed to read navigation file"
|
|
1297
1347
|
});
|
|
1298
1348
|
}
|
|
1299
|
-
|
|
1300
|
-
try {
|
|
1301
|
-
|
|
1349
|
+
const profileFilesToCheck = [...changes.profiles.new, ...changes.profiles.changed];
|
|
1350
|
+
for (const file of profileFilesToCheck) try {
|
|
1351
|
+
const profile = await readPortalFile$1(portalDir, file);
|
|
1352
|
+
if (profile.navigation && !validNavSlugs.has(profile.navigation)) errors.push({
|
|
1353
|
+
file,
|
|
1354
|
+
message: `References navigation "${profile.navigation}" which does not exist`
|
|
1355
|
+
});
|
|
1356
|
+
if (profile.mobile_navigation && !validNavSlugs.has(profile.mobile_navigation)) errors.push({
|
|
1357
|
+
file,
|
|
1358
|
+
message: `References mobile_navigation "${profile.mobile_navigation}" which does not exist`
|
|
1359
|
+
});
|
|
1360
|
+
for (const themeSlug of profile.themes) if (!validThemeSlugs.has(themeSlug)) errors.push({
|
|
1361
|
+
file,
|
|
1362
|
+
message: `References theme "${themeSlug}" which does not exist`
|
|
1363
|
+
});
|
|
1302
1364
|
} catch {
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
message: "Failed to
|
|
1306
|
-
details: `Output was: ${output.slice(0, 200)}`
|
|
1365
|
+
errors.push({
|
|
1366
|
+
file,
|
|
1367
|
+
message: "Failed to read profile file"
|
|
1307
1368
|
});
|
|
1308
1369
|
}
|
|
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);
|
|
1370
|
+
return errors;
|
|
1319
1371
|
}
|
|
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;
|
|
1372
|
+
/**
|
|
1373
|
+
* Build a set of valid slugs for a resource type by combining
|
|
1374
|
+
* existing mapping slugs with local file slugs on disk.
|
|
1375
|
+
*/
|
|
1376
|
+
function buildValidSlugsSet(portalDir, resourceType, mappings) {
|
|
1377
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
1378
|
+
for (const slug of Object.keys(mappings[resourceType])) slugs.add(slug);
|
|
1379
|
+
const dir = join(portalDir, resourceType);
|
|
1380
|
+
if (existsSync(dir)) try {
|
|
1381
|
+
const entries = readdirSync(dir);
|
|
1382
|
+
for (const entry of entries) if (entry.endsWith(".json")) slugs.add(basename(entry, ".json"));
|
|
1383
|
+
} catch {}
|
|
1384
|
+
return slugs;
|
|
1816
1385
|
}
|
|
1817
1386
|
/**
|
|
1818
1387
|
* Recursively validate navigation item screen references.
|
|
@@ -1949,7 +1518,7 @@ async function runPortalGitSync(options) {
|
|
|
1949
1518
|
await gitFetch(cwd, remoteUrl, branch);
|
|
1950
1519
|
await gitAdoptFetchHead(cwd);
|
|
1951
1520
|
}
|
|
1952
|
-
if (await hasUncommittedChanges(cwd)) await gitAutoCommit(cwd, commitMessage);
|
|
1521
|
+
if (await hasUncommittedChanges(cwd)) await gitAutoCommit(cwd, commitMessage, { ignoreRemovals: true });
|
|
1953
1522
|
return await pushCommitted(cwd, remoteUrl, branch);
|
|
1954
1523
|
}
|
|
1955
1524
|
await gitFetch(cwd, remoteUrl, branch);
|
|
@@ -2094,7 +1663,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
|
|
|
2094
1663
|
const slug = slugFromPath(file);
|
|
2095
1664
|
try {
|
|
2096
1665
|
const local = await readPortalFile(portalDir, file);
|
|
2097
|
-
const newId = (await fluid_os_v0_create_fluid_osscreen(client, defId, { screen: {
|
|
1666
|
+
const newId = (await fluidOs.fluid_os_v0_create_fluid_osscreen(client, defId, { screen: {
|
|
2098
1667
|
name: local.name,
|
|
2099
1668
|
slug,
|
|
2100
1669
|
component_tree: toApiComponentTree(local.component_tree)
|
|
@@ -2135,7 +1704,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
|
|
|
2135
1704
|
}
|
|
2136
1705
|
try {
|
|
2137
1706
|
const local = await readPortalFile(portalDir, file);
|
|
2138
|
-
await fluid_os_v0_update_fluid_osscreen(client, defId, screenId, { screen: {
|
|
1707
|
+
await fluidOs.fluid_os_v0_update_fluid_osscreen(client, defId, screenId, { screen: {
|
|
2139
1708
|
name: local.name,
|
|
2140
1709
|
slug,
|
|
2141
1710
|
component_tree: toApiComponentTree(local.component_tree)
|
|
@@ -2174,7 +1743,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
|
|
|
2174
1743
|
};
|
|
2175
1744
|
}
|
|
2176
1745
|
try {
|
|
2177
|
-
await fluid_os_v0_delete_fluid_osscreen(client, defId, screenId);
|
|
1746
|
+
await fluidOs.fluid_os_v0_delete_fluid_osscreen(client, defId, screenId);
|
|
2178
1747
|
currentMappings = removeMapping(currentMappings, "screens", slug);
|
|
2179
1748
|
results.push({
|
|
2180
1749
|
file,
|
|
@@ -2209,7 +1778,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
|
|
|
2209
1778
|
const slug = slugFromPath(file);
|
|
2210
1779
|
try {
|
|
2211
1780
|
const local = await readPortalFile(portalDir, file);
|
|
2212
|
-
const newId = (await fluid_os_v0_create_fluid_ostheme(client, defId, { theme: {
|
|
1781
|
+
const newId = (await fluidOs.fluid_os_v0_create_fluid_ostheme(client, defId, { theme: {
|
|
2213
1782
|
name: local.name,
|
|
2214
1783
|
active: local.active,
|
|
2215
1784
|
config: local.config
|
|
@@ -2250,7 +1819,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
|
|
|
2250
1819
|
}
|
|
2251
1820
|
try {
|
|
2252
1821
|
const local = await readPortalFile(portalDir, file);
|
|
2253
|
-
await fluid_os_v0_update_fluid_ostheme(client, defId, themeId, { theme: {
|
|
1822
|
+
await fluidOs.fluid_os_v0_update_fluid_ostheme(client, defId, themeId, { theme: {
|
|
2254
1823
|
name: local.name,
|
|
2255
1824
|
active: local.active,
|
|
2256
1825
|
config: local.config
|
|
@@ -2289,7 +1858,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
|
|
|
2289
1858
|
};
|
|
2290
1859
|
}
|
|
2291
1860
|
try {
|
|
2292
|
-
await fluid_os_v0_delete_fluid_ostheme(client, defId, themeId);
|
|
1861
|
+
await fluidOs.fluid_os_v0_delete_fluid_ostheme(client, defId, themeId);
|
|
2293
1862
|
currentMappings = removeMapping(currentMappings, "themes", slug);
|
|
2294
1863
|
results.push({
|
|
2295
1864
|
file,
|
|
@@ -2359,7 +1928,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2359
1928
|
const slug = slugFromPath(file);
|
|
2360
1929
|
try {
|
|
2361
1930
|
const local = await readPortalFile(portalDir, file);
|
|
2362
|
-
const newId = (await fluid_os_v0_create_fluid_osnavigation(client, defId, { navigation: {
|
|
1931
|
+
const newId = (await fluidOs.fluid_os_v0_create_fluid_osnavigation(client, defId, { navigation: {
|
|
2363
1932
|
name: local.name,
|
|
2364
1933
|
platform: local.platform
|
|
2365
1934
|
} })).navigation?.id;
|
|
@@ -2369,7 +1938,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2369
1938
|
const localToServerId = /* @__PURE__ */ new Map();
|
|
2370
1939
|
for (const item of resolvedItems) {
|
|
2371
1940
|
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: {
|
|
1941
|
+
const created = await fluidOs.fluid_os_v0_create_fluid_osnavigation_item(client, defId, newId, { navigation_item: {
|
|
2373
1942
|
label: item.label ?? "",
|
|
2374
1943
|
position: item.position ?? 0,
|
|
2375
1944
|
icon: item.icon ?? void 0,
|
|
@@ -2416,15 +1985,15 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2416
1985
|
}
|
|
2417
1986
|
try {
|
|
2418
1987
|
const local = await readPortalFile(portalDir, file);
|
|
2419
|
-
await fluid_os_v0_update_fluid_osnavigation(client, defId, navId, { navigation: {
|
|
1988
|
+
await fluidOs.fluid_os_v0_update_fluid_osnavigation(client, defId, navId, { navigation: {
|
|
2420
1989
|
name: local.name,
|
|
2421
1990
|
platform: local.platform
|
|
2422
1991
|
} });
|
|
2423
1992
|
const resolvedItems = flattenNavigationItems(resolveNavigationItemScreenIds(local.navigation_items, currentMappings));
|
|
2424
|
-
const serverItems = (await fluid_os_v0_list_fluid_osnavigation_items(client, defId, navId)).navigation_items ?? [];
|
|
1993
|
+
const serverItems = (await fluidOs.fluid_os_v0_list_fluid_osnavigation_items(client, defId, navId)).navigation_items ?? [];
|
|
2425
1994
|
const serverById = new Map(serverItems.map((s) => [s.id, s]));
|
|
2426
1995
|
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);
|
|
1996
|
+
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
1997
|
const localToServerId = /* @__PURE__ */ new Map();
|
|
2429
1998
|
for (const item of resolvedItems) {
|
|
2430
1999
|
const resolvedParentId = item.parent_id != null ? localToServerId.get(item.parent_id) ?? item.parent_id : void 0;
|
|
@@ -2437,9 +2006,9 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2437
2006
|
source: item.source ?? void 0,
|
|
2438
2007
|
parent_id: resolvedParentId
|
|
2439
2008
|
};
|
|
2440
|
-
if (item.id && serverById.has(item.id)) await fluid_os_v0_update_fluid_osnavigation_item(client, defId, navId, item.id, { navigation_item: body });
|
|
2009
|
+
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
2010
|
else {
|
|
2442
|
-
const created = await fluid_os_v0_create_fluid_osnavigation_item(client, defId, navId, { navigation_item: {
|
|
2011
|
+
const created = await fluidOs.fluid_os_v0_create_fluid_osnavigation_item(client, defId, navId, { navigation_item: {
|
|
2443
2012
|
...body,
|
|
2444
2013
|
label: body.label ?? "",
|
|
2445
2014
|
position: body.position ?? 0
|
|
@@ -2481,7 +2050,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
|
|
|
2481
2050
|
};
|
|
2482
2051
|
}
|
|
2483
2052
|
try {
|
|
2484
|
-
await fluid_os_v0_delete_fluid_osnavigation(client, defId, navId);
|
|
2053
|
+
await fluidOs.fluid_os_v0_delete_fluid_osnavigation(client, defId, navId);
|
|
2485
2054
|
currentMappings = removeMapping(currentMappings, "navigations", slug);
|
|
2486
2055
|
results.push({
|
|
2487
2056
|
file,
|
|
@@ -2515,7 +2084,8 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
|
|
|
2515
2084
|
for (const file of changes.new) {
|
|
2516
2085
|
const slug = slugFromPath(file);
|
|
2517
2086
|
try {
|
|
2518
|
-
const
|
|
2087
|
+
const body = resolveProfileBody(await readPortalFile(portalDir, file), currentMappings);
|
|
2088
|
+
const newId = (await fluidOs.fluid_os_v0_create_fluid_osprofile(client, defId, { profile: body })).profile?.id;
|
|
2519
2089
|
if (newId != null) currentMappings = updateMapping(currentMappings, "profiles", slug, newId);
|
|
2520
2090
|
results.push({
|
|
2521
2091
|
file,
|
|
@@ -2551,7 +2121,8 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
|
|
|
2551
2121
|
};
|
|
2552
2122
|
}
|
|
2553
2123
|
try {
|
|
2554
|
-
|
|
2124
|
+
const body = resolveProfileBody(await readPortalFile(portalDir, file), currentMappings);
|
|
2125
|
+
await fluidOs.fluid_os_v0_update_fluid_osprofile(client, defId, profileId, { profile: body });
|
|
2555
2126
|
results.push({
|
|
2556
2127
|
file,
|
|
2557
2128
|
action: "updated",
|
|
@@ -2586,7 +2157,7 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
|
|
|
2586
2157
|
};
|
|
2587
2158
|
}
|
|
2588
2159
|
try {
|
|
2589
|
-
await fluid_os_v0_delete_fluid_osprofile(client, defId, profileId);
|
|
2160
|
+
await fluidOs.fluid_os_v0_delete_fluid_osprofile(client, defId, profileId);
|
|
2590
2161
|
currentMappings = removeMapping(currentMappings, "profiles", slug);
|
|
2591
2162
|
results.push({
|
|
2592
2163
|
file,
|
|
@@ -2843,7 +2414,7 @@ async function selectDefinition(client, appName) {
|
|
|
2843
2414
|
const definitions = [];
|
|
2844
2415
|
let page = 1;
|
|
2845
2416
|
while (true) {
|
|
2846
|
-
const response = await fluid_os_v0_list_fluid_osdefinitions(client, {
|
|
2417
|
+
const response = await fluidOs.fluid_os_v0_list_fluid_osdefinitions(client, {
|
|
2847
2418
|
page,
|
|
2848
2419
|
per_page: 500
|
|
2849
2420
|
});
|
|
@@ -2988,138 +2559,340 @@ function insertImport(source, importLine) {
|
|
|
2988
2559
|
if (lineEnd === -1) return source + "\n" + importLine + "\n";
|
|
2989
2560
|
return source.slice(0, lineEnd + 1) + importLine + "\n" + source.slice(lineEnd + 1);
|
|
2990
2561
|
}
|
|
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;
|
|
2562
|
+
/** Insert a widget definition into a defineWidgetPackage widgets array. */
|
|
2563
|
+
function insertIntoWidgetPackage(source, exportName) {
|
|
2564
|
+
const packageStart = source.lastIndexOf("defineWidgetPackage(");
|
|
2565
|
+
if (packageStart === -1) return null;
|
|
2566
|
+
const packageSource = source.slice(packageStart);
|
|
2567
|
+
const pattern = /(widgets\s*:\s*)\[([^\]]*)\]/g;
|
|
3001
2568
|
let lastMatch = null;
|
|
3002
2569
|
let m;
|
|
3003
|
-
while ((m = pattern.exec(
|
|
2570
|
+
while ((m = pattern.exec(packageSource)) !== null) lastMatch = m;
|
|
3004
2571
|
if (!lastMatch) return null;
|
|
3005
2572
|
const fullMatch = lastMatch[0];
|
|
3006
2573
|
const declaration = lastMatch[1];
|
|
3007
2574
|
const inner = lastMatch[2];
|
|
3008
|
-
const matchStart = lastMatch.index;
|
|
2575
|
+
const matchStart = packageStart + lastMatch.index;
|
|
3009
2576
|
const lines = inner.split("\n");
|
|
3010
2577
|
const existingEntries = [];
|
|
3011
2578
|
for (const line of lines) {
|
|
3012
2579
|
const trimmed = line.trim();
|
|
3013
2580
|
if (trimmed && !trimmed.startsWith("//")) existingEntries.push(trimmed.replace(/,$/, ""));
|
|
3014
2581
|
}
|
|
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);
|
|
2582
|
+
if (existingEntries.includes(exportName)) return source;
|
|
2583
|
+
const commentLines = lines.filter((line) => line.trim().startsWith("//")).map((line) => line.trimEnd());
|
|
2584
|
+
const replacement = `${declaration}[${commentLines.length > 0 ? "\n" + commentLines.join("\n") : ""}\n${[...existingEntries, exportName].map((e) => ` ${e},`).join("\n")}\n]`;
|
|
2585
|
+
return source.slice(0, matchStart) + replacement + source.slice(matchStart + fullMatch.length);
|
|
2586
|
+
}
|
|
2587
|
+
//#endregion
|
|
2588
|
+
//#region src/commands/widget-create.ts
|
|
2589
|
+
const WIDGET_CONFIG_PATH$1 = path.join("src", "widgets.config.ts");
|
|
2590
|
+
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) => {
|
|
2591
|
+
const cwd = process.cwd();
|
|
2592
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
2593
|
+
const configPath = path.join(cwd, WIDGET_CONFIG_PATH$1);
|
|
2594
|
+
const widgetDir = path.join(cwd, "src", "widgets", name);
|
|
2595
|
+
const validationError = validateWidgetName(name);
|
|
2596
|
+
if (validationError) {
|
|
2597
|
+
console.error(chalk.red(`Error: ${validationError}`));
|
|
2598
|
+
process.exit(1);
|
|
2599
|
+
}
|
|
2600
|
+
if (!await fs.pathExists(packageJsonPath)) {
|
|
2601
|
+
console.error(chalk.red("Error: No package.json found."));
|
|
2602
|
+
console.error(chalk.yellow("Run this command from a Fluid portal project root."));
|
|
2603
|
+
process.exit(1);
|
|
2604
|
+
}
|
|
2605
|
+
if (await fs.pathExists(widgetDir)) {
|
|
2606
|
+
console.error(chalk.red(`Error: Widget directory already exists: src/widgets/${name}`));
|
|
2607
|
+
process.exit(1);
|
|
2608
|
+
}
|
|
2609
|
+
const widgetType = toWidgetType(name);
|
|
2610
|
+
const componentName = toComponentName(widgetType);
|
|
2611
|
+
const camelName = toCamelCase(name);
|
|
2612
|
+
const exportName = camelName.endsWith("Widget") ? camelName : `${camelName}Widget`;
|
|
2613
|
+
const displayName = toDisplayName(name);
|
|
2614
|
+
const category = options.category ?? "components";
|
|
2615
|
+
const importLine = `import { ${exportName} } from "./widgets/${name}";`;
|
|
2616
|
+
try {
|
|
2617
|
+
await fs.ensureDir(widgetDir);
|
|
2618
|
+
await fs.writeFile(path.join(widgetDir, "index.tsx"), createWidgetSource({
|
|
2619
|
+
name,
|
|
2620
|
+
widgetType,
|
|
2621
|
+
componentName,
|
|
2622
|
+
exportName,
|
|
2623
|
+
displayName,
|
|
2624
|
+
category
|
|
2625
|
+
}), "utf-8");
|
|
2626
|
+
if (await fs.pathExists(configPath)) {
|
|
2627
|
+
const configSource = await fs.readFile(configPath, "utf-8");
|
|
2628
|
+
if (isEmptyWidgetConfig(configSource)) await fs.writeFile(configPath, createWidgetConfigSource(importLine, exportName), "utf-8");
|
|
2629
|
+
else {
|
|
2630
|
+
const withImport = insertImport(configSource, importLine);
|
|
2631
|
+
const updated = withImport ? insertIntoWidgetPackage(withImport, exportName) : null;
|
|
2632
|
+
if (!updated) throw new Error(`${WIDGET_CONFIG_PATH$1} must export the starter widgetPackages placeholder or contain a defineWidgetPackage({ widgets: [...] }) declaration.`);
|
|
2633
|
+
await fs.writeFile(configPath, updated, "utf-8");
|
|
2634
|
+
}
|
|
2635
|
+
} else await fs.writeFile(configPath, createWidgetConfigSource(importLine, exportName), "utf-8");
|
|
2636
|
+
} catch (error) {
|
|
2637
|
+
await fs.remove(widgetDir).catch(() => {});
|
|
2638
|
+
console.error(chalk.red("Error: Failed to scaffold Remote DOM widget."));
|
|
2639
|
+
console.error(error);
|
|
2640
|
+
process.exit(1);
|
|
2641
|
+
}
|
|
2642
|
+
console.log();
|
|
2643
|
+
console.log(chalk.green("Created widget:") + ` src/widgets/${name}/index.tsx`);
|
|
2644
|
+
console.log(chalk.green("Registered") + ` in ${chalk.cyan(WIDGET_CONFIG_PATH$1)}`);
|
|
2645
|
+
console.log();
|
|
2646
|
+
console.log(chalk.yellow("Next steps:"));
|
|
2647
|
+
console.log(` 1. Edit ${chalk.cyan(`src/widgets/${name}/index.tsx`)}`);
|
|
2648
|
+
console.log(` 2. Run ${chalk.cyan("fluid portal deploy --dry-run")} to verify the package`);
|
|
2649
|
+
});
|
|
2650
|
+
function createWidgetSource(options) {
|
|
2651
|
+
return `import { defineWidget } from "@fluid-app/portal-sdk/widgets/worker";
|
|
2652
|
+
|
|
2653
|
+
interface ${options.widgetType}Props {
|
|
2654
|
+
readonly title?: string;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
function ${options.componentName}({ title = ${JSON.stringify(options.displayName)} }: ${options.widgetType}Props) {
|
|
2658
|
+
return (
|
|
2659
|
+
<section>
|
|
2660
|
+
<h3>{title}</h3>
|
|
2661
|
+
<p>Edit this widget in src/widgets/${options.name}/index.tsx</p>
|
|
2662
|
+
</section>
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
export const ${options.exportName} = defineWidget({
|
|
2667
|
+
name: ${JSON.stringify(options.componentName)},
|
|
2668
|
+
component: ${options.componentName},
|
|
2669
|
+
displayName: ${JSON.stringify(options.displayName)},
|
|
2670
|
+
description: ${JSON.stringify(`A custom ${options.displayName.toLowerCase()} widget`)},
|
|
2671
|
+
icon: "puzzle-piece",
|
|
2672
|
+
category: ${JSON.stringify(options.category)},
|
|
2673
|
+
propertySchema: {
|
|
2674
|
+
fields: [{ key: "title", label: "Title", type: "text" }],
|
|
2675
|
+
},
|
|
2676
|
+
defaultProps: {
|
|
2677
|
+
title: ${JSON.stringify(options.displayName)},
|
|
2678
|
+
},
|
|
2679
|
+
});
|
|
2680
|
+
`;
|
|
2681
|
+
}
|
|
2682
|
+
function isEmptyWidgetConfig(source) {
|
|
2683
|
+
return /export\s+const\s+widgetPackages\s*=\s*\[\]\s+as\s+const\s*;/u.test(source);
|
|
2684
|
+
}
|
|
2685
|
+
function createWidgetConfigSource(importLine, exportName) {
|
|
2686
|
+
return `import { defineWidgetPackage } from "@fluid-app/portal-sdk/widgets/worker";
|
|
2687
|
+
${importLine}
|
|
2688
|
+
|
|
2689
|
+
export const widgetPackage = defineWidgetPackage({
|
|
2690
|
+
scope: "company",
|
|
2691
|
+
packageStableId: "portal-widgets",
|
|
2692
|
+
version: "0.0.0-dev",
|
|
2693
|
+
widgets: [${exportName}],
|
|
2694
|
+
});
|
|
2695
|
+
`;
|
|
2696
|
+
}
|
|
2697
|
+
const widgetCommand = new Command("widget").description("Manage Remote DOM portal widgets").addCommand(createSubcommand);
|
|
2698
|
+
//#endregion
|
|
2699
|
+
//#region src/utils/widget-package-config.ts
|
|
2700
|
+
const WIDGET_CONFIG_PATH = "src/widgets.config.ts";
|
|
2701
|
+
const SOURCE_PACKAGE_EXTRACT_FILENAME = "extract-widget-packages.ts";
|
|
2702
|
+
const SOURCE_PACKAGE_OUTPUT_FILENAME = "source-widget-packages.json";
|
|
2703
|
+
const SOURCE_PACKAGE_OUTPUT_SENTINEL = "fluid-widget-source-packages:v1";
|
|
2704
|
+
const TSX_CLI_PATH = createRequire(import.meta.url).resolve("tsx/cli");
|
|
2705
|
+
async function resolvePortalWidgetSourceConfig(projectDir) {
|
|
2706
|
+
const candidate = path.join(projectDir, WIDGET_CONFIG_PATH);
|
|
2707
|
+
return await fs.pathExists(candidate) ? {
|
|
2708
|
+
path: candidate,
|
|
2709
|
+
relativePath: WIDGET_CONFIG_PATH
|
|
2710
|
+
} : void 0;
|
|
2711
|
+
}
|
|
2712
|
+
async function loadSourceWidgetPackages(projectDir) {
|
|
2713
|
+
const config = await resolvePortalWidgetSourceConfig(projectDir);
|
|
2714
|
+
if (!config) return success([]);
|
|
2715
|
+
let tempDir;
|
|
2716
|
+
try {
|
|
2717
|
+
tempDir = await createTempDirectory(projectDir);
|
|
2718
|
+
const extractFile = path.join(tempDir, SOURCE_PACKAGE_EXTRACT_FILENAME);
|
|
2719
|
+
const outputFile = path.join(tempDir, SOURCE_PACKAGE_OUTPUT_FILENAME);
|
|
2720
|
+
await fs.writeFile(extractFile, createSourcePackageExtractorScript({
|
|
2721
|
+
projectDir,
|
|
2722
|
+
configPath: config.path
|
|
2723
|
+
}), {
|
|
2724
|
+
encoding: "utf-8",
|
|
2725
|
+
flag: "wx"
|
|
2726
|
+
});
|
|
2727
|
+
await execa(process.execPath, [
|
|
2728
|
+
TSX_CLI_PATH,
|
|
2729
|
+
extractFile,
|
|
2730
|
+
outputFile
|
|
2731
|
+
], {
|
|
2732
|
+
cwd: projectDir,
|
|
2733
|
+
stdio: "pipe",
|
|
2734
|
+
env: {
|
|
2735
|
+
...process.env,
|
|
2736
|
+
NODE_ENV: "production"
|
|
2737
|
+
}
|
|
2738
|
+
});
|
|
2739
|
+
const outputResult = await readSourcePackageExtractorOutput(outputFile);
|
|
2740
|
+
if (!outputResult.success) return outputResult;
|
|
2741
|
+
const parsed = outputResult.value;
|
|
2742
|
+
if (parsed.length === 0) return success([]);
|
|
2743
|
+
return success(parsed);
|
|
2744
|
+
} catch (err) {
|
|
2745
|
+
const error = err;
|
|
2746
|
+
return failure({
|
|
2747
|
+
code: "CONFIG_LOAD_FAILED",
|
|
2748
|
+
message: `Failed to load widget packages from ${config.relativePath}`,
|
|
2749
|
+
details: error.stderr ?? error.message ?? String(err)
|
|
2750
|
+
});
|
|
2751
|
+
} finally {
|
|
2752
|
+
if (tempDir) await fs.remove(tempDir).catch(() => {});
|
|
2753
|
+
}
|
|
3019
2754
|
}
|
|
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);
|
|
2755
|
+
async function createTempDirectory(projectDir) {
|
|
2756
|
+
const projectTmpDir = path.join(projectDir, ".fluid", "tmp");
|
|
2757
|
+
try {
|
|
2758
|
+
await fs.ensureDir(projectTmpDir);
|
|
2759
|
+
return await fs.mkdtemp(path.join(projectTmpDir, "widget-packages-"));
|
|
2760
|
+
} catch (err) {
|
|
2761
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2762
|
+
throw new Error(`Unable to create project-local temporary directory at ${projectTmpDir}: ${message}`);
|
|
3034
2763
|
}
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
2764
|
+
}
|
|
2765
|
+
async function readSourcePackageExtractorOutput(outputFile) {
|
|
2766
|
+
let output;
|
|
2767
|
+
try {
|
|
2768
|
+
output = await fs.readFile(outputFile, "utf-8");
|
|
2769
|
+
} catch (err) {
|
|
2770
|
+
return failure({
|
|
2771
|
+
code: "CONFIG_LOAD_FAILED",
|
|
2772
|
+
message: "Widget package extractor did not write an output file",
|
|
2773
|
+
details: err instanceof Error ? err.message : String(err)
|
|
2774
|
+
});
|
|
3038
2775
|
}
|
|
3039
|
-
|
|
3040
|
-
const componentName = toComponentName(widgetType);
|
|
3041
|
-
const displayName = toDisplayName(name);
|
|
3042
|
-
const category = options.category ?? "components";
|
|
2776
|
+
let parsed;
|
|
3043
2777
|
try {
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
2778
|
+
parsed = JSON.parse(output);
|
|
2779
|
+
} catch {
|
|
2780
|
+
return failure({
|
|
2781
|
+
code: "INVALID_FORMAT",
|
|
2782
|
+
message: "Failed to parse widget package output file as JSON",
|
|
2783
|
+
details: `Output was: ${output.slice(0, 200)}`
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
if (!isRecord$4(parsed) || parsed.sentinel !== SOURCE_PACKAGE_OUTPUT_SENTINEL) return failure({
|
|
2787
|
+
code: "INVALID_FORMAT",
|
|
2788
|
+
message: "Widget package extractor output file had an invalid sentinel"
|
|
2789
|
+
});
|
|
2790
|
+
if (!Array.isArray(parsed.data)) return failure({
|
|
2791
|
+
code: "INVALID_FORMAT",
|
|
2792
|
+
message: "Extracted widget package output is not an array",
|
|
2793
|
+
details: `Expected an array, got: ${typeof parsed.data}`
|
|
2794
|
+
});
|
|
2795
|
+
return success(parsed.data);
|
|
3047
2796
|
}
|
|
2797
|
+
function createSourcePackageExtractorScript(options) {
|
|
2798
|
+
return `
|
|
2799
|
+
import fs from "node:fs/promises";
|
|
2800
|
+
import path from "node:path";
|
|
2801
|
+
import { createServer, normalizePath } from "vite";
|
|
3048
2802
|
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
);
|
|
2803
|
+
const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
|
|
2804
|
+
const OUTPUT_SENTINEL = ${JSON.stringify(SOURCE_PACKAGE_OUTPUT_SENTINEL)};
|
|
2805
|
+
const projectRoot = ${JSON.stringify(options.projectDir)};
|
|
2806
|
+
const widgetConfigPath = ${JSON.stringify(options.configPath)};
|
|
2807
|
+
const outputPath = process.argv[2];
|
|
2808
|
+
if (!outputPath) throw new Error("Missing widget package extractor output path.");
|
|
2809
|
+
|
|
2810
|
+
function toViteModuleId(modulePath) {
|
|
2811
|
+
const relativePath = path.relative(projectRoot, modulePath);
|
|
2812
|
+
if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
2813
|
+
return "/" + normalizePath(relativePath);
|
|
2814
|
+
}
|
|
2815
|
+
return normalizePath(modulePath);
|
|
3058
2816
|
}
|
|
3059
|
-
`);
|
|
3060
|
-
await fs.writeFile(path.join(widgetDir, "manifest.ts"), `import type { WidgetManifest } from "@fluid-app/portal-sdk";
|
|
3061
|
-
import { ${componentName} } from "./component";
|
|
3062
2817
|
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
widgetType: "${widgetType}",
|
|
3073
|
-
displayName: "${displayName}",
|
|
3074
|
-
fields: [{ key: "title", label: "Title", type: "text" }],
|
|
2818
|
+
const server = await createServer({
|
|
2819
|
+
root: projectRoot,
|
|
2820
|
+
mode: "production",
|
|
2821
|
+
server: { middlewareMode: true },
|
|
2822
|
+
appType: "custom",
|
|
2823
|
+
logLevel: "error",
|
|
2824
|
+
clearScreen: false,
|
|
2825
|
+
resolve: {
|
|
2826
|
+
conditions: ["module", "browser", "development|production"],
|
|
3075
2827
|
},
|
|
3076
|
-
|
|
3077
|
-
|
|
2828
|
+
ssr: {
|
|
2829
|
+
noExternal: ["@fluid-app/portal-sdk"],
|
|
2830
|
+
resolve: {
|
|
2831
|
+
conditions: ["module", "node", "development|production"],
|
|
2832
|
+
},
|
|
3078
2833
|
},
|
|
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
2834
|
});
|
|
3122
|
-
|
|
2835
|
+
|
|
2836
|
+
try {
|
|
2837
|
+
const widgetConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
|
|
2838
|
+
|
|
2839
|
+
function isSourceWidgetPackage(value) {
|
|
2840
|
+
return Boolean(value && typeof value === "object" && value[SOURCE_PACKAGE_MARKER] === true);
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
function collectSourceWidgetPackages(mod) {
|
|
2844
|
+
const candidates = [
|
|
2845
|
+
mod.widgetPackage,
|
|
2846
|
+
...(Array.isArray(mod.widgetPackages) ? mod.widgetPackages : []),
|
|
2847
|
+
mod.default,
|
|
2848
|
+
];
|
|
2849
|
+
const byPackageId = new Map();
|
|
2850
|
+
for (const candidate of candidates) {
|
|
2851
|
+
if (!isSourceWidgetPackage(candidate)) continue;
|
|
2852
|
+
if (!byPackageId.has(candidate.packageId)) byPackageId.set(candidate.packageId, candidate);
|
|
2853
|
+
}
|
|
2854
|
+
return Array.from(byPackageId.values());
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
function serializeWidget(widget) {
|
|
2858
|
+
if (!widget || typeof widget !== "object" || Array.isArray(widget)) {
|
|
2859
|
+
return widget;
|
|
2860
|
+
}
|
|
2861
|
+
const { component, ...metadata } = widget;
|
|
2862
|
+
return metadata;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
function serializePackage(sourcePackage) {
|
|
2866
|
+
return {
|
|
2867
|
+
manifestVersion: sourcePackage.manifestVersion,
|
|
2868
|
+
scope: sourcePackage.scope,
|
|
2869
|
+
packageStableId: sourcePackage.packageStableId,
|
|
2870
|
+
packageId: sourcePackage.packageId,
|
|
2871
|
+
packageType: sourcePackage.packageType,
|
|
2872
|
+
version: sourcePackage.version,
|
|
2873
|
+
cssUrls: sourcePackage.cssUrls,
|
|
2874
|
+
widgets: Array.isArray(sourcePackage.widgets)
|
|
2875
|
+
? sourcePackage.widgets.map(serializeWidget)
|
|
2876
|
+
: sourcePackage.widgets,
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
await fs.writeFile(
|
|
2881
|
+
outputPath,
|
|
2882
|
+
JSON.stringify({
|
|
2883
|
+
sentinel: OUTPUT_SENTINEL,
|
|
2884
|
+
data: collectSourceWidgetPackages(widgetConfig).map(serializePackage),
|
|
2885
|
+
}),
|
|
2886
|
+
"utf-8",
|
|
2887
|
+
);
|
|
2888
|
+
} finally {
|
|
2889
|
+
await server.close();
|
|
2890
|
+
}
|
|
2891
|
+
`;
|
|
2892
|
+
}
|
|
2893
|
+
function isRecord$4(value) {
|
|
2894
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2895
|
+
}
|
|
3123
2896
|
//#endregion
|
|
3124
2897
|
//#region src/utils/widget-package-artifacts.ts
|
|
3125
2898
|
const WIDGET_PACKAGE_BUILDER_VERSION = "1";
|
|
@@ -3240,7 +3013,7 @@ const DEFAULT_MIN_SDK_VERSION = "0.0.0";
|
|
|
3240
3013
|
function validateSingleSourceWidgetPackage(sourcePackages, options = {}) {
|
|
3241
3014
|
if (sourcePackages.length === 0) return validationFailure({
|
|
3242
3015
|
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
|
|
3016
|
+
message: "No defineWidgetPackage source package was found. Export widgetPackage, widgetPackages, or a default widget package from src/widgets.config.ts."
|
|
3244
3017
|
});
|
|
3245
3018
|
if (sourcePackages.length > 1) return validationFailure({
|
|
3246
3019
|
code: "MULTIPLE_SOURCE_PACKAGES",
|
|
@@ -3342,22 +3115,19 @@ function validateSingleSourceWidgetPackage(sourcePackages, options = {}) {
|
|
|
3342
3115
|
errors: []
|
|
3343
3116
|
};
|
|
3344
3117
|
}
|
|
3345
|
-
function
|
|
3346
|
-
const remoteEntryUrl = options.remoteEntryUrl ?? "widget.js";
|
|
3347
|
-
validateRuntimeDescriptorUrl(remoteEntryUrl, "remoteEntryUrl");
|
|
3118
|
+
function buildWidgetPackagePublishManifest(validated, options = {}) {
|
|
3348
3119
|
return {
|
|
3349
3120
|
manifestVersion: WIDGET_PACKAGE_MANIFEST_VERSION,
|
|
3350
|
-
|
|
3351
|
-
|
|
3121
|
+
runtime: "remote-dom",
|
|
3122
|
+
packageId: validated.packageId,
|
|
3123
|
+
source: validated.owner,
|
|
3352
3124
|
version: validated.version,
|
|
3353
|
-
|
|
3125
|
+
workerEntryUrl: toWidgetPackageArtifactPath("widget.js"),
|
|
3354
3126
|
cssUrls: mergeCssUrls(validated.sourcePackage.cssUrls, options.cssUrls),
|
|
3355
|
-
|
|
3127
|
+
assetUrls: [],
|
|
3128
|
+
widgets: validated.widgets.map(({ name: _name, ...widget }) => widget)
|
|
3356
3129
|
};
|
|
3357
3130
|
}
|
|
3358
|
-
function getWidgetPackageDescriptorPackageId(validated) {
|
|
3359
|
-
return validated.owner === "droplet" ? validated.packageKey : validated.packageId;
|
|
3360
|
-
}
|
|
3361
3131
|
function sourceWidgetToDescriptor(widget, type) {
|
|
3362
3132
|
const displayName = widget.displayName ?? widget.name;
|
|
3363
3133
|
return {
|
|
@@ -3371,9 +3141,16 @@ function sourceWidgetToDescriptor(widget, type) {
|
|
|
3371
3141
|
defaultProps: widget.defaultProps ?? {},
|
|
3372
3142
|
container: normalizeContainer(widget.container),
|
|
3373
3143
|
minSdkVersion: widget.minSdkVersion ?? DEFAULT_MIN_SDK_VERSION,
|
|
3374
|
-
resizable: normalizeResizable(widget.resizable)
|
|
3144
|
+
resizable: normalizeResizable(widget.resizable),
|
|
3145
|
+
capabilities: normalizeCapabilities(widget.capabilities ?? [])
|
|
3375
3146
|
};
|
|
3376
3147
|
}
|
|
3148
|
+
function normalizeCapabilities(capabilities) {
|
|
3149
|
+
return capabilities.map((capability) => ({
|
|
3150
|
+
name: capability.name,
|
|
3151
|
+
version: capability.version ?? "1"
|
|
3152
|
+
}));
|
|
3153
|
+
}
|
|
3377
3154
|
function validationFailure(error) {
|
|
3378
3155
|
return {
|
|
3379
3156
|
success: false,
|
|
@@ -3426,8 +3203,9 @@ function validateSourcePackageShape(value) {
|
|
|
3426
3203
|
validateOptionalStringField(widget, "icon", `${widgetPath}.icon`, errors, { requireNonEmpty: true });
|
|
3427
3204
|
validateOptionalStringField(widget, "category", `${widgetPath}.category`, errors, { requireNonEmpty: true });
|
|
3428
3205
|
validateOptionalStringField(widget, "minSdkVersion", `${widgetPath}.minSdkVersion`, errors, { requireNonEmpty: true });
|
|
3429
|
-
|
|
3430
|
-
|
|
3206
|
+
validateOptionalJsonRecordField(widget, "propertySchema", `${widgetPath}.propertySchema`, errors);
|
|
3207
|
+
validateOptionalJsonRecordField(widget, "defaultProps", `${widgetPath}.defaultProps`, errors);
|
|
3208
|
+
validateOptionalCapabilities(widget, "capabilities", `${widgetPath}.capabilities`, errors);
|
|
3431
3209
|
});
|
|
3432
3210
|
return errors;
|
|
3433
3211
|
}
|
|
@@ -3473,85 +3251,67 @@ function validateCssUrl(value, cssUrlPath, errors) {
|
|
|
3473
3251
|
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
3252
|
});
|
|
3475
3253
|
}
|
|
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) {
|
|
3254
|
+
function validateOptionalJsonRecordField(record, key, path, errors) {
|
|
3547
3255
|
const value = record[key];
|
|
3548
|
-
if (value === void 0
|
|
3256
|
+
if (value === void 0) return;
|
|
3257
|
+
if (isRecord$3(value) && isJsonValue(value)) return;
|
|
3549
3258
|
errors.push({
|
|
3550
3259
|
code: "INVALID_WIDGET_METADATA",
|
|
3551
3260
|
path,
|
|
3552
3261
|
message: `${path} must be a JSON object when present.`
|
|
3553
3262
|
});
|
|
3554
3263
|
}
|
|
3264
|
+
function validateOptionalCapabilities(record, key, path, errors) {
|
|
3265
|
+
const value = record[key];
|
|
3266
|
+
if (value === void 0) return;
|
|
3267
|
+
if (!Array.isArray(value)) {
|
|
3268
|
+
errors.push({
|
|
3269
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3270
|
+
path,
|
|
3271
|
+
message: `${path} must be an array when present.`
|
|
3272
|
+
});
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
const names = /* @__PURE__ */ new Set();
|
|
3276
|
+
value.forEach((candidate, index) => {
|
|
3277
|
+
const capabilityPath = `${path}[${index}]`;
|
|
3278
|
+
if (!isRecord$3(candidate)) {
|
|
3279
|
+
errors.push({
|
|
3280
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3281
|
+
path: capabilityPath,
|
|
3282
|
+
message: `${capabilityPath} must be a JSON object.`
|
|
3283
|
+
});
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
const name = candidate.name;
|
|
3287
|
+
if (!isCanonicalMetadataString(name)) {
|
|
3288
|
+
errors.push({
|
|
3289
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3290
|
+
path: `${capabilityPath}.name`,
|
|
3291
|
+
message: `${capabilityPath}.name must be a non-empty string without surrounding whitespace or control characters.`
|
|
3292
|
+
});
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
3295
|
+
const version = candidate.version;
|
|
3296
|
+
if (version !== void 0 && !isCanonicalMetadataString(version)) errors.push({
|
|
3297
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3298
|
+
path: `${capabilityPath}.version`,
|
|
3299
|
+
message: `${capabilityPath}.version must be a non-empty string without surrounding whitespace or control characters when present.`
|
|
3300
|
+
});
|
|
3301
|
+
if (names.has(name)) {
|
|
3302
|
+
errors.push({
|
|
3303
|
+
code: "INVALID_WIDGET_METADATA",
|
|
3304
|
+
path: `${capabilityPath}.name`,
|
|
3305
|
+
message: `${path} must not contain duplicate capability name ${JSON.stringify(name)}.`
|
|
3306
|
+
});
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
names.add(name);
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
function isCanonicalMetadataString(value) {
|
|
3313
|
+
return typeof value === "string" && value.length > 0 && value === value.trim() && !/\p{Cc}/u.test(value);
|
|
3314
|
+
}
|
|
3555
3315
|
function mergeCssUrls(sourceCssUrls, generatedCssUrls) {
|
|
3556
3316
|
const generatedCssUrlSet = new Set(generatedCssUrls ?? []);
|
|
3557
3317
|
generatedCssUrls?.forEach((cssUrl, index) => {
|
|
@@ -3562,7 +3322,10 @@ function mergeCssUrls(sourceCssUrls, generatedCssUrls) {
|
|
|
3562
3322
|
if (generatedCssUrlSet.has(cssUrl)) return;
|
|
3563
3323
|
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
3324
|
});
|
|
3565
|
-
return Array.from(new Set([...sourceCssUrls ?? [], ...generatedCssUrls ?? []]));
|
|
3325
|
+
return Array.from(new Set([...sourceCssUrls ?? [], ...generatedCssUrls ?? []])).map(toWidgetPackageArtifactPath);
|
|
3326
|
+
}
|
|
3327
|
+
function toWidgetPackageArtifactPath(value) {
|
|
3328
|
+
return value;
|
|
3566
3329
|
}
|
|
3567
3330
|
function validateGeneratedCssArtifactUrl(value, cssUrlPath) {
|
|
3568
3331
|
if (value.trim().length === 0) throw new Error(`${cssUrlPath} must be a non-empty string.`);
|
|
@@ -3639,15 +3402,7 @@ const PROJECT_VITE_CONFIG_CANDIDATES = [
|
|
|
3639
3402
|
"vite.config.js",
|
|
3640
3403
|
"vite.config.mjs"
|
|
3641
3404
|
];
|
|
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";
|
|
3405
|
+
const BLOCKED_FLUID_PROJECT_PLUGIN_NAMES = ["fluid-portal-dev", "fluid-backend-dev"];
|
|
3651
3406
|
const require = createRequire(import.meta.url);
|
|
3652
3407
|
function validateSharedWidgetPackagePublishDir(projectDir, publishDir = DEFAULT_PUBLISH_DIR, options = {}) {
|
|
3653
3408
|
const label = options.optionName ?? "publishDir";
|
|
@@ -3686,9 +3441,11 @@ async function buildSharedWidgetPackage(options) {
|
|
|
3686
3441
|
try {
|
|
3687
3442
|
await validateWidgetPackageOutputPaths(options.projectDir, publishDirInput);
|
|
3688
3443
|
const publishDir = path.resolve(options.projectDir, publishDirInput);
|
|
3444
|
+
const widgetScriptPath = path.join(publishDir, "widget.js");
|
|
3689
3445
|
await fs.emptyDir(publishDir);
|
|
3690
3446
|
const tempDir = path.resolve(options.projectDir, TEMP_BUILD_DIR);
|
|
3691
3447
|
await fs.emptyDir(tempDir);
|
|
3448
|
+
let buildFailure;
|
|
3692
3449
|
try {
|
|
3693
3450
|
await buildWidgetScriptBundle({
|
|
3694
3451
|
projectDir: options.projectDir,
|
|
@@ -3696,15 +3453,25 @@ async function buildSharedWidgetPackage(options) {
|
|
|
3696
3453
|
publishDir,
|
|
3697
3454
|
validated: validation.value
|
|
3698
3455
|
});
|
|
3699
|
-
}
|
|
3700
|
-
|
|
3456
|
+
} catch (error) {
|
|
3457
|
+
buildFailure = { error };
|
|
3458
|
+
}
|
|
3459
|
+
try {
|
|
3460
|
+
await fs.remove(tempDir);
|
|
3461
|
+
} catch {
|
|
3462
|
+
const cleanupFailure = /* @__PURE__ */ new Error("Failed to remove reserved temporary build directory.");
|
|
3463
|
+
if (buildFailure) {
|
|
3464
|
+
const primaryError = buildFailure.error instanceof Error ? buildFailure.error : new Error(String(buildFailure.error));
|
|
3465
|
+
throw new AggregateError([primaryError, cleanupFailure], `${primaryError.message}\n${cleanupFailure.message}`);
|
|
3466
|
+
}
|
|
3467
|
+
throw cleanupFailure;
|
|
3701
3468
|
}
|
|
3469
|
+
if (buildFailure) throw buildFailure.error;
|
|
3702
3470
|
const cssUrls = await collectCssUrls(publishDir);
|
|
3703
|
-
const descriptor =
|
|
3471
|
+
const descriptor = buildWidgetPackagePublishManifest(validation.value, { cssUrls });
|
|
3704
3472
|
const manifestContent = stableStringify(descriptor);
|
|
3705
3473
|
const publishPackageId = descriptor.packageId;
|
|
3706
3474
|
const manifestPath = path.join(publishDir, "manifest.json");
|
|
3707
|
-
const widgetScriptPath = path.join(publishDir, "widget.js");
|
|
3708
3475
|
const publishManifestPath = path.join(publishDir, "publish-manifest.json");
|
|
3709
3476
|
await fs.writeFile(manifestPath, manifestContent, "utf-8");
|
|
3710
3477
|
const artifacts = await collectWidgetPackageArtifacts(publishDir);
|
|
@@ -3719,7 +3486,7 @@ async function buildSharedWidgetPackage(options) {
|
|
|
3719
3486
|
await fs.writeFile(publishManifestPath, stableStringify(publishManifest), "utf-8");
|
|
3720
3487
|
return success({
|
|
3721
3488
|
publishDir,
|
|
3722
|
-
packageId:
|
|
3489
|
+
packageId: validation.value.packageId,
|
|
3723
3490
|
version: validation.value.version,
|
|
3724
3491
|
manifestPath,
|
|
3725
3492
|
publishManifestPath,
|
|
@@ -3736,13 +3503,12 @@ async function buildSharedWidgetPackage(options) {
|
|
|
3736
3503
|
}
|
|
3737
3504
|
}
|
|
3738
3505
|
function createWidgetPackageEntrySource(options) {
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
import {
|
|
3506
|
+
return `import { createElement } from "react";
|
|
3507
|
+
import { startWidgetPackage } from "@fluid-app/portal-sdk/widgets/worker";
|
|
3508
|
+
${options.cssImportPath ? `import ${JSON.stringify(options.cssImportPath)};` : ""}
|
|
3742
3509
|
|
|
3510
|
+
const widgetConfig = await import(${JSON.stringify(options.configImportPath)});
|
|
3743
3511
|
const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
|
|
3744
|
-
const WIDGET_PACKAGE_ID = ${JSON.stringify(runtimePackageId)};
|
|
3745
|
-
const WIDGET_PACKAGE_VERSION = ${JSON.stringify(options.validated.version)};
|
|
3746
3512
|
const descriptors = ${JSON.stringify(options.validated.widgets, null, 2)};
|
|
3747
3513
|
|
|
3748
3514
|
function isSourceWidgetPackage(value) {
|
|
@@ -3759,50 +3525,25 @@ function collectSourceWidgetPackages(mod) {
|
|
|
3759
3525
|
|
|
3760
3526
|
const sourcePackage = collectSourceWidgetPackages(widgetConfig)[0];
|
|
3761
3527
|
if (!sourcePackage) {
|
|
3762
|
-
throw new Error("No Fluid source widget package found while loading widget package bundle.");
|
|
3528
|
+
throw new Error("No Fluid source widget package found while loading widget package worker bundle.");
|
|
3763
3529
|
}
|
|
3764
3530
|
|
|
3765
3531
|
const componentsByName = new Map(
|
|
3766
3532
|
sourcePackage.widgets.map((widget) => [widget.name, widget.component]),
|
|
3767
3533
|
);
|
|
3768
3534
|
|
|
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
3535
|
const widgets = descriptors.map((descriptor) => {
|
|
3789
3536
|
const component = componentsByName.get(descriptor.name);
|
|
3790
|
-
if (typeof component !== "function"
|
|
3791
|
-
throw new Error("Widget package is missing component for " + descriptor.name + ".");
|
|
3537
|
+
if (typeof component !== "function") {
|
|
3538
|
+
throw new Error("Widget package is missing Remote DOM component for " + descriptor.name + ".");
|
|
3792
3539
|
}
|
|
3793
|
-
return {
|
|
3540
|
+
return {
|
|
3541
|
+
...descriptor,
|
|
3542
|
+
component: (props) => createElement(component, props),
|
|
3543
|
+
};
|
|
3794
3544
|
});
|
|
3795
3545
|
|
|
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
|
-
});
|
|
3546
|
+
startWidgetPackage({ widgets });
|
|
3806
3547
|
`;
|
|
3807
3548
|
}
|
|
3808
3549
|
async function buildWidgetScriptBundle(options) {
|
|
@@ -3810,14 +3551,14 @@ async function buildWidgetScriptBundle(options) {
|
|
|
3810
3551
|
if (!sourceConfig) throw new Error("No portal widget source config found.");
|
|
3811
3552
|
const entryPath = path.join(options.tempDir, "widget-entry.ts");
|
|
3812
3553
|
const viteConfigPath = path.join(options.tempDir, "vite.config.mjs");
|
|
3554
|
+
const rootStylesPath = path.join(options.projectDir, "styles.css");
|
|
3813
3555
|
await fs.writeFile(entryPath, createWidgetPackageEntrySource({
|
|
3814
3556
|
configImportPath: pathToFileURL(sourceConfig.path).href,
|
|
3557
|
+
cssImportPath: await fs.pathExists(rootStylesPath) ? pathToFileURL(rootStylesPath).href : void 0,
|
|
3815
3558
|
validated: options.validated
|
|
3816
3559
|
}), "utf-8");
|
|
3817
3560
|
await fs.writeFile(viteConfigPath, createViteConfigSource({
|
|
3818
3561
|
entryPath,
|
|
3819
|
-
packageId: getWidgetPackageDescriptorPackageId(options.validated),
|
|
3820
|
-
packageVersion: options.validated.version,
|
|
3821
3562
|
publishDir: options.publishDir,
|
|
3822
3563
|
projectConfigPath: await resolveProjectViteConfigPath(options.projectDir)
|
|
3823
3564
|
}), "utf-8");
|
|
@@ -3832,43 +3573,9 @@ async function buildWidgetScriptBundle(options) {
|
|
|
3832
3573
|
stdio: "pipe",
|
|
3833
3574
|
env: {
|
|
3834
3575
|
...process.env,
|
|
3835
|
-
NODE_ENV: "production"
|
|
3836
|
-
NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
|
|
3576
|
+
NODE_ENV: "production"
|
|
3837
3577
|
}
|
|
3838
3578
|
});
|
|
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
3579
|
}
|
|
3873
3580
|
function resolveViteCliPath(projectDir) {
|
|
3874
3581
|
const projectViteCliPath = resolveViteCliPathFromRequire(createRequire(path.join(projectDir, "package.json")));
|
|
@@ -3896,6 +3603,9 @@ export default defineConfig(${widgetConfigSource});
|
|
|
3896
3603
|
|
|
3897
3604
|
const projectConfigPath = ${JSON.stringify(options.projectConfigPath)};
|
|
3898
3605
|
const widgetConfig = ${widgetConfigSource};
|
|
3606
|
+
const fluidWidgetDefineInvariants = {
|
|
3607
|
+
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
3608
|
+
};
|
|
3899
3609
|
const blockedFluidPluginNames = new Set(${JSON.stringify(BLOCKED_FLUID_PROJECT_PLUGIN_NAMES)});
|
|
3900
3610
|
|
|
3901
3611
|
function isAllowedProjectPlugin(plugin) {
|
|
@@ -3936,30 +3646,35 @@ function sanitizeProjectConfig(config) {
|
|
|
3936
3646
|
return sanitized;
|
|
3937
3647
|
}
|
|
3938
3648
|
|
|
3649
|
+
function copyConfigDefineValue(value) {
|
|
3650
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
3651
|
+
? { ...value }
|
|
3652
|
+
: {};
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
function mergeFluidWidgetDefineInvariants(mergedConfig, safeProjectConfig, widgetConfig) {
|
|
3656
|
+
mergedConfig.define = {
|
|
3657
|
+
...copyConfigDefineValue(safeProjectConfig.define),
|
|
3658
|
+
...copyConfigDefineValue(mergedConfig.define),
|
|
3659
|
+
...copyConfigDefineValue(widgetConfig.define),
|
|
3660
|
+
...fluidWidgetDefineInvariants,
|
|
3661
|
+
};
|
|
3662
|
+
return mergedConfig;
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3939
3665
|
export default defineConfig(async (configEnv) => {
|
|
3940
3666
|
const projectConfig = await loadConfigFromFile(configEnv, projectConfigPath);
|
|
3941
3667
|
const safeProjectConfig = sanitizeProjectConfig(projectConfig?.config ?? {});
|
|
3942
|
-
return mergeConfig(safeProjectConfig, widgetConfig);
|
|
3668
|
+
return mergeFluidWidgetDefineInvariants(mergeConfig(safeProjectConfig, widgetConfig), safeProjectConfig, widgetConfig);
|
|
3943
3669
|
});
|
|
3944
3670
|
`;
|
|
3945
3671
|
}
|
|
3946
3672
|
function createWidgetViteConfigObjectSource(options) {
|
|
3947
3673
|
const configFileSource = options.disableConfigFileLookup ? " configFile: false,\n" : "";
|
|
3948
3674
|
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-]+(?:\\([^)]*\\))?$/;
|
|
3675
|
+
const fluidWidgetDefineInvariants = {
|
|
3676
|
+
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
3677
|
+
};
|
|
3963
3678
|
|
|
3964
3679
|
function createFluidWidgetBuildInvariants() {
|
|
3965
3680
|
return {
|
|
@@ -3967,26 +3682,18 @@ function createWidgetViteConfigObjectSource(options) {
|
|
|
3967
3682
|
emptyOutDir: false,
|
|
3968
3683
|
outDir: ${JSON.stringify(options.publishDir)},
|
|
3969
3684
|
sourcemap: false,
|
|
3685
|
+
target: "esnext",
|
|
3686
|
+
cssCodeSplit: false,
|
|
3970
3687
|
lib: {
|
|
3971
3688
|
entry: ${JSON.stringify(options.entryPath)},
|
|
3972
|
-
formats: ["
|
|
3973
|
-
name: "FluidWidgetPackage",
|
|
3689
|
+
formats: ["es"],
|
|
3974
3690
|
fileName: () => "widget.js",
|
|
3691
|
+
cssFileName: "widget",
|
|
3975
3692
|
},
|
|
3976
3693
|
rollupOptions: {
|
|
3977
|
-
external: [
|
|
3978
|
-
"react",
|
|
3979
|
-
"react-dom",
|
|
3980
|
-
"react/jsx-runtime",
|
|
3981
|
-
"@fluid-app/portal-sdk",
|
|
3982
|
-
],
|
|
3983
3694
|
output: {
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
"react-dom": "FluidShared.ReactDOM",
|
|
3987
|
-
"react/jsx-runtime": "FluidShared.ReactJsxRuntime",
|
|
3988
|
-
"@fluid-app/portal-sdk": "FluidShared.PortalSdk",
|
|
3989
|
-
},
|
|
3695
|
+
inlineDynamicImports: true,
|
|
3696
|
+
hoistTransitiveImports: false,
|
|
3990
3697
|
},
|
|
3991
3698
|
},
|
|
3992
3699
|
};
|
|
@@ -4000,1179 +3707,26 @@ function createWidgetViteConfigObjectSource(options) {
|
|
|
4000
3707
|
return { build: createFluidWidgetBuildInvariants() };
|
|
4001
3708
|
},
|
|
4002
3709
|
configResolved(config) {
|
|
3710
|
+
config.define = {
|
|
3711
|
+
...(config.define ?? {}),
|
|
3712
|
+
...fluidWidgetDefineInvariants,
|
|
3713
|
+
};
|
|
4003
3714
|
Object.assign(config.build, createFluidWidgetBuildInvariants());
|
|
4004
3715
|
},
|
|
4005
3716
|
};
|
|
4006
3717
|
}
|
|
4007
3718
|
|
|
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
3719
|
return {
|
|
5166
3720
|
${configFileSource} publicDir: false,
|
|
3721
|
+
define: fluidWidgetDefineInvariants,
|
|
5167
3722
|
resolve: {
|
|
5168
3723
|
conditions: [
|
|
5169
|
-
"fluid-widget-authoring",
|
|
5170
3724
|
"module",
|
|
5171
3725
|
"browser",
|
|
5172
3726
|
"development|production",
|
|
5173
3727
|
],
|
|
5174
3728
|
},
|
|
5175
|
-
plugins: [fluidWidgetBuildInvariantsPlugin()
|
|
3729
|
+
plugins: [fluidWidgetBuildInvariantsPlugin()],
|
|
5176
3730
|
build: createFluidWidgetBuildInvariants(),
|
|
5177
3731
|
};
|
|
5178
3732
|
})()`;
|
|
@@ -5223,12 +3777,22 @@ function stableStringify(value) {
|
|
|
5223
3777
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
5224
3778
|
}
|
|
5225
3779
|
function readCliVersion() {
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
3780
|
+
return resolveCliVersionFromPackageMetadata(path.dirname(fileURLToPath(import.meta.url)), require);
|
|
3781
|
+
}
|
|
3782
|
+
function resolveCliVersionFromPackageMetadata(startDir, loadPackageMetadata) {
|
|
3783
|
+
for (const packageJsonPath of candidatePackageJsonPaths(startDir)) {
|
|
3784
|
+
let packageJson;
|
|
3785
|
+
try {
|
|
3786
|
+
packageJson = loadPackageMetadata(packageJsonPath);
|
|
3787
|
+
} catch (error) {
|
|
3788
|
+
if (isModuleResolutionError(error)) continue;
|
|
3789
|
+
throw new Error("Failed to read Fluid CLI package metadata.", { cause: error });
|
|
3790
|
+
}
|
|
3791
|
+
if (!isRecord$2(packageJson) || packageJson.name !== "@fluid-app/fluid-cli-portal") continue;
|
|
3792
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) throw new Error("Fluid CLI package metadata does not declare a valid version.");
|
|
3793
|
+
return packageJson.version;
|
|
3794
|
+
}
|
|
3795
|
+
throw new Error("Unable to find Fluid CLI package metadata.");
|
|
5232
3796
|
}
|
|
5233
3797
|
function resolveViteCliPathFromRequire(moduleRequire) {
|
|
5234
3798
|
try {
|
|
@@ -5276,22 +3840,6 @@ var WidgetPackageUploadError = class extends Error {
|
|
|
5276
3840
|
this.code = code;
|
|
5277
3841
|
}
|
|
5278
3842
|
};
|
|
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
3843
|
function buildWidgetPackageArtifactPayload(artifact) {
|
|
5296
3844
|
return {
|
|
5297
3845
|
path: artifact.path,
|
|
@@ -5333,7 +3881,7 @@ function getWidgetPackageCompleteUploadRoute(owner, version) {
|
|
|
5333
3881
|
async function requestWidgetPackageUploadSession(client, owner, request) {
|
|
5334
3882
|
const payload = buildWidgetPackageUploadSessionPayload(request);
|
|
5335
3883
|
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);
|
|
3884
|
+
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
3885
|
} catch (err) {
|
|
5338
3886
|
if (err instanceof WidgetPackageUploadError) throw err;
|
|
5339
3887
|
throw new WidgetPackageUploadError("SESSION_REQUEST_FAILED", `Failed to create widget package upload session: ${formatUnknownError(err)}`);
|
|
@@ -5372,7 +3920,7 @@ async function uploadWidgetPackageArtifacts(session, artifacts, fetchImpl = fetc
|
|
|
5372
3920
|
async function completeWidgetPackageUpload(client, owner, version, packageKey, artifacts) {
|
|
5373
3921
|
const payload = buildWidgetPackageCompleteUploadPayload(packageKey, artifacts);
|
|
5374
3922
|
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);
|
|
3923
|
+
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
3924
|
} catch (err) {
|
|
5377
3925
|
if (err instanceof WidgetPackageUploadError) throw err;
|
|
5378
3926
|
throw new WidgetPackageUploadError("COMPLETE_UPLOAD_FAILED", `Failed to complete widget package upload: ${formatUnknownError(err)}`);
|
|
@@ -5395,128 +3943,53 @@ async function publishWidgetPackageVersion(options) {
|
|
|
5395
3943
|
};
|
|
5396
3944
|
}
|
|
5397
3945
|
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
|
|
3946
|
+
const record = requireRecord(response, "INVALID_SESSION_RESPONSE", "Upload session response must be a JSON object.");
|
|
3947
|
+
requireFiniteNumberProperty(record, "version_id", "INVALID_SESSION_RESPONSE");
|
|
3948
|
+
requireStringProperty(record, "artifact_base_url", "INVALID_SESSION_RESPONSE");
|
|
3949
|
+
const uploadValues = record["uploads"];
|
|
3950
|
+
if (!Array.isArray(uploadValues)) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", "Upload session response must contain an \"uploads\" array.");
|
|
3951
|
+
const requestedArtifactsByPath = new Map(requestedArtifacts.map((artifact) => [artifact.path, artifact]));
|
|
3952
|
+
const seenUploadPaths = /* @__PURE__ */ new Set();
|
|
3953
|
+
const uploads = uploadValues.map((value, index) => {
|
|
3954
|
+
const upload = requireRecord(value, "INVALID_SESSION_RESPONSE", `Upload session uploads[${index}] must be a JSON object.`);
|
|
3955
|
+
const path = requireStringProperty(upload, "path", "INVALID_SESSION_RESPONSE");
|
|
3956
|
+
const url = requireStringProperty(upload, "upload_url", "INVALID_SESSION_RESPONSE");
|
|
3957
|
+
requireStringProperty(upload, "public_url", "INVALID_SESSION_RESPONSE");
|
|
3958
|
+
const requestedArtifact = requestedArtifactsByPath.get(path);
|
|
3959
|
+
if (!requestedArtifact) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response included unexpected artifact "${path}".`);
|
|
3960
|
+
if (seenUploadPaths.has(path)) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response included duplicate artifact "${path}".`);
|
|
3961
|
+
seenUploadPaths.add(path);
|
|
3962
|
+
return {
|
|
3963
|
+
path,
|
|
3964
|
+
url,
|
|
3965
|
+
contentType: requestedArtifact.contentType,
|
|
3966
|
+
headers: {}
|
|
3967
|
+
};
|
|
3968
|
+
});
|
|
3969
|
+
const missingUploadPaths = requestedArtifacts.map((artifact) => artifact.path).filter((artifactPath) => !seenUploadPaths.has(artifactPath));
|
|
5405
3970
|
if (missingUploadPaths.length > 0) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response is missing signed URLs for artifact(s): ${missingUploadPaths.join(", ")}.`);
|
|
5406
3971
|
return {
|
|
5407
|
-
version,
|
|
3972
|
+
version: requestedVersion,
|
|
5408
3973
|
uploads,
|
|
5409
3974
|
raw: response
|
|
5410
3975
|
};
|
|
5411
3976
|
}
|
|
5412
3977
|
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");
|
|
3978
|
+
const record = requireRecord(response, "INVALID_COMPLETE_RESPONSE", "Complete upload response must be a JSON object.");
|
|
3979
|
+
const responseVersion = requireStringProperty(record, "version", "INVALID_COMPLETE_RESPONSE");
|
|
3980
|
+
if (responseVersion !== requestedVersion) throw new WidgetPackageUploadError("INVALID_COMPLETE_RESPONSE", `Complete upload response version "${responseVersion}" did not match requested version "${requestedVersion}".`);
|
|
3981
|
+
const packageKey = requireStringProperty(record, "packageId", "INVALID_COMPLETE_RESPONSE");
|
|
3982
|
+
const status = requireStringProperty(record, "status", "INVALID_COMPLETE_RESPONSE");
|
|
3983
|
+
requireStringProperty(record, "scriptUrl", "INVALID_COMPLETE_RESPONSE");
|
|
3984
|
+
requireStringProperty(record, "manifestUrl", "INVALID_COMPLETE_RESPONSE");
|
|
3985
|
+
requireStringArrayProperty(record, "cssUrls", "INVALID_COMPLETE_RESPONSE");
|
|
5423
3986
|
return {
|
|
5424
|
-
version: responseVersion
|
|
5425
|
-
|
|
5426
|
-
|
|
3987
|
+
version: responseVersion,
|
|
3988
|
+
status,
|
|
3989
|
+
packageKey,
|
|
5427
3990
|
raw: response
|
|
5428
3991
|
};
|
|
5429
3992
|
}
|
|
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
3993
|
function normalizeHeaderName(name) {
|
|
5521
3994
|
return name.toLowerCase();
|
|
5522
3995
|
}
|
|
@@ -5529,57 +4002,47 @@ function mergeUploadHeaders(headers, contentType) {
|
|
|
5529
4002
|
merged["Content-Type"] = contentType;
|
|
5530
4003
|
return merged;
|
|
5531
4004
|
}
|
|
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);
|
|
4005
|
+
function requireRecord(value, code, message) {
|
|
4006
|
+
if (!isRecord$1(value) || Array.isArray(value)) throw new WidgetPackageUploadError(code, message);
|
|
5542
4007
|
return value;
|
|
5543
4008
|
}
|
|
5544
4009
|
function isRecord$1(value) {
|
|
5545
4010
|
return typeof value === "object" && value !== null;
|
|
5546
4011
|
}
|
|
5547
|
-
function
|
|
4012
|
+
function requireStringProperty(record, key, code) {
|
|
5548
4013
|
const value = record[key];
|
|
5549
4014
|
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;
|
|
4015
|
+
throw new WidgetPackageUploadError(code, `Response field "${key}" must be a non-empty string.`);
|
|
4016
|
+
}
|
|
4017
|
+
function requireFiniteNumberProperty(record, key, code) {
|
|
4018
|
+
const value = record[key];
|
|
4019
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4020
|
+
throw new WidgetPackageUploadError(code, `Response field "${key}" must be a finite number.`);
|
|
4021
|
+
}
|
|
4022
|
+
function requireStringArrayProperty(record, key, code) {
|
|
4023
|
+
const value = record[key];
|
|
4024
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.trim().length > 0)) return value;
|
|
4025
|
+
throw new WidgetPackageUploadError(code, `Response field "${key}" must be an array of non-empty strings.`);
|
|
5577
4026
|
}
|
|
5578
4027
|
function formatUnknownError(err) {
|
|
5579
4028
|
const base = err instanceof Error ? err.message : String(err);
|
|
5580
|
-
if (isRecord$1(err) && "data" in err) return `${base} — ${
|
|
4029
|
+
if (isRecord$1(err) && "data" in err) return `${base} — ${safeStringify(err.data)}`;
|
|
5581
4030
|
return base;
|
|
5582
4031
|
}
|
|
4032
|
+
function safeStringify(value) {
|
|
4033
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
4034
|
+
try {
|
|
4035
|
+
return JSON.stringify(value, (_key, item) => {
|
|
4036
|
+
if (typeof item === "bigint") return `${item}n`;
|
|
4037
|
+
if (typeof item !== "object" || item === null) return item;
|
|
4038
|
+
if (seen.has(item)) return "[Circular]";
|
|
4039
|
+
seen.add(item);
|
|
4040
|
+
return item;
|
|
4041
|
+
}) ?? String(value);
|
|
4042
|
+
} catch {
|
|
4043
|
+
return "[unserializable error data]";
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
5583
4046
|
//#endregion
|
|
5584
4047
|
//#region src/commands/widget-package-publish.ts
|
|
5585
4048
|
async function publishWidgetRuntimeArtifacts(options, dependencies = {}) {
|
|
@@ -6074,7 +4537,7 @@ const createVersionCommand = new Command("create").description("Create a new ver
|
|
|
6074
4537
|
console.log(chalk.bold("Creating version..."));
|
|
6075
4538
|
let result;
|
|
6076
4539
|
try {
|
|
6077
|
-
result = await fluid_os_v0_create_fluid_osversion(client, definitionId);
|
|
4540
|
+
result = await fluidOs.fluid_os_v0_create_fluid_osversion(client, definitionId);
|
|
6078
4541
|
} catch (err) {
|
|
6079
4542
|
console.error(chalk.red("Error:") + " Failed to create version — " + (err instanceof Error ? err.message : String(err)));
|
|
6080
4543
|
process.exit(1);
|
|
@@ -6092,7 +4555,7 @@ const createVersionCommand = new Command("create").description("Create a new ver
|
|
|
6092
4555
|
if (options.activate) {
|
|
6093
4556
|
console.log("Activating version...");
|
|
6094
4557
|
try {
|
|
6095
|
-
await fluid_os_v0_update_fluid_osversion(client, definitionId, version.id, { version: { active: true } });
|
|
4558
|
+
await fluidOs.fluid_os_v0_update_fluid_osversion(client, definitionId, version.id, { version: { active: true } });
|
|
6096
4559
|
} catch (err) {
|
|
6097
4560
|
console.error(chalk.red("Error:") + " Failed to activate version — " + (err instanceof Error ? err.message : String(err)));
|
|
6098
4561
|
process.exit(1);
|
|
@@ -6107,7 +4570,7 @@ const listVersionCommand = new Command("list").description("List all versions of
|
|
|
6107
4570
|
const definitionId = await requireDefinitionId();
|
|
6108
4571
|
let result;
|
|
6109
4572
|
try {
|
|
6110
|
-
result = await fluid_os_v0_list_fluid_osversions(client, definitionId);
|
|
4573
|
+
result = await fluidOs.fluid_os_v0_list_fluid_osversions(client, definitionId);
|
|
6111
4574
|
} catch (err) {
|
|
6112
4575
|
console.error(chalk.red("Error:") + " Failed to list versions — " + (err instanceof Error ? err.message : String(err)));
|
|
6113
4576
|
process.exit(1);
|
|
@@ -6150,7 +4613,7 @@ const activateVersionCommand = new Command("activate").description("Activate a s
|
|
|
6150
4613
|
console.log();
|
|
6151
4614
|
console.log(chalk.bold("Activating version..."));
|
|
6152
4615
|
try {
|
|
6153
|
-
await fluid_os_v0_update_fluid_osversion(client, definitionId, versionId, { version: { active: true } });
|
|
4616
|
+
await fluidOs.fluid_os_v0_update_fluid_osversion(client, definitionId, versionId, { version: { active: true } });
|
|
6154
4617
|
} catch (err) {
|
|
6155
4618
|
console.error(chalk.red("Error:") + " Failed to activate version — " + (err instanceof Error ? err.message : String(err)));
|
|
6156
4619
|
process.exit(1);
|
|
@@ -6233,6 +4696,6 @@ const plugin = {
|
|
|
6233
4696
|
}
|
|
6234
4697
|
};
|
|
6235
4698
|
//#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,
|
|
4699
|
+
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
4700
|
|
|
6238
4701
|
//# sourceMappingURL=index.mjs.map
|