@openfairygui/functions 0.2.0-alpha.11 → 0.2.0-alpha.13

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/src/codegen.ts CHANGED
@@ -12,8 +12,9 @@ import {
12
12
  UNITY_BINDER_TEMPLATE,
13
13
  UNITY_COMPONENT_TEMPLATE,
14
14
  } from './codegen-templates.js';
15
- import { formatPluginError, type LoadedPlugin } from './plugins/loader.js';
16
- import type { CliCodeGenerationSettings, PublishFileSystem, RootProjectSettings } from './shared-types.js';
15
+ import { formatPluginError, type LoadedPlugin } from './plugins/types.js';
16
+ import type { PublishFileSystem } from './publish/contracts.js';
17
+ import type { CliCodeGenerationSettings, RootProjectSettings } from './shared-types.js';
17
18
 
18
19
  export const AUTO_GENERATED_CODE_MARK = '/** This is an automatically generated class by FairyGUI. Please do not modify it. **/';
19
20
  const DEFAULT_CLASS_NAME_PREFIX = 'UI_';
package/src/index.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  export { inspect, type InspectReport, type InspectCategoryReport } from './inspect.js';
2
- export { validate, type ValidateOptions, type ValidationResult, type ValidationIssue, ValidationSeverity } from './validate.js';
2
+ export {
3
+ validate,
4
+ type ValidateOptions,
5
+ type ValidationResult,
6
+ type ValidationIssue,
7
+ ValidationSeverity,
8
+ } from './validate.js';
3
9
  export { prune, type PruneOptions } from './prune.js';
4
10
  export { rename, type RenameOptions } from './rename.js';
5
11
  export { atlas, type AtlasOptions } from './atlas.js';
@@ -17,15 +23,26 @@ export {
17
23
  decodeText,
18
24
  } from './codegen.js';
19
25
 
20
-
21
26
  export type {
22
27
  CodeWriter,
23
28
  ICodeWriterConfig,
24
29
  MaybePromise,
30
+ LoadedPlugin,
25
31
  Plugin,
26
32
  PluginManifest,
27
33
  PluginModule,
28
34
  } from './plugins/types.js';
35
+ export type {
36
+ AtlasRasterBackend,
37
+ AtlasRasterCompositeInput,
38
+ AtlasRasterInput,
39
+ AtlasRasterMetadata,
40
+ AtlasRasterPipeline,
41
+ AtlasRasterResolvedBuffer,
42
+ PublishFileSystem,
43
+ PublishOutputFileSystem,
44
+ PublishSourceFileSystem,
45
+ } from './publish/contracts.js';
29
46
  export {
30
47
  restore,
31
48
  type RestoreFileSystem,
@@ -60,7 +77,6 @@ export type {
60
77
  HasOptionalSrc,
61
78
  HasOptionalUrl,
62
79
  PublishDependency,
63
- PublishFileSystem,
64
80
  RootProjectSettings,
65
81
  CliCodeGenerationSettings,
66
82
  } from './shared-types.js';
package/src/node.ts ADDED
@@ -0,0 +1,4 @@
1
+ export {
2
+ publishNode,
3
+ type PublishNodeOptions,
4
+ } from './adapters/node/publish.js';
@@ -44,4 +44,13 @@ export interface Plugin {
44
44
  onPublishEnd?: (doc: Document, options: PublishOptions) => MaybePromise<void>;
45
45
  }
46
46
 
47
+ export interface LoadedPlugin {
48
+ name: string;
49
+ plugin: Plugin;
50
+ }
51
+
47
52
  export type PluginModule = Plugin & { default?: Plugin };
53
+
54
+ export function formatPluginError(error: unknown): string {
55
+ return error instanceof Error ? error.message : String(error);
56
+ }
@@ -0,0 +1,80 @@
1
+ import type { FileSystem } from '@openfairygui/core';
2
+
3
+ /**
4
+ * Source files required by a publish adapter.
5
+ *
6
+ * The host owns this filesystem: Node adapters use native paths while web
7
+ * adapters can use File System Access, OPFS, IndexedDB, ZIP, or memory.
8
+ */
9
+ export type PublishSourceFileSystem = Pick<FileSystem, 'readFileRaw' | 'join'>;
10
+
11
+ /**
12
+ * Output files required by a publish adapter.
13
+ */
14
+ export type PublishOutputFileSystem = Pick<FileSystem, 'writeFileRaw' | 'mkdir' | 'join'>;
15
+
16
+ /**
17
+ * Full filesystem contract consumed by the capability-injected publish core.
18
+ *
19
+ * Read, enumeration, and delete operations are optional because individual
20
+ * publish lanes only request them when needed.
21
+ */
22
+ export type PublishFileSystem = PublishOutputFileSystem & {
23
+ deleteFile?: (path: string) => Promise<void>;
24
+ exists?: FileSystem['exists'];
25
+ readdir?: FileSystem['readdir'];
26
+ readFileRaw?: FileSystem['readFileRaw'];
27
+ };
28
+
29
+ export interface AtlasRasterMetadata {
30
+ width?: number;
31
+ height?: number;
32
+ channels?: number;
33
+ hasAlpha?: boolean;
34
+ trimOffsetLeft?: number;
35
+ trimOffsetTop?: number;
36
+ }
37
+
38
+ export interface AtlasRasterResolvedBuffer {
39
+ data: Uint8Array;
40
+ info: Required<Pick<AtlasRasterMetadata, 'width' | 'height' | 'channels'>> & AtlasRasterMetadata;
41
+ }
42
+
43
+ export interface AtlasRasterCompositeInput {
44
+ input: Uint8Array;
45
+ left: number;
46
+ top: number;
47
+ }
48
+
49
+ export type AtlasRasterInput =
50
+ | string
51
+ | Uint8Array
52
+ | {
53
+ create: {
54
+ width: number;
55
+ height: number;
56
+ channels: 4;
57
+ background: { r: number; g: number; b: number; alpha: number };
58
+ };
59
+ };
60
+
61
+ /**
62
+ * Host-provided raster pipeline used by atlas packing.
63
+ *
64
+ * Sharp and the browser Canvas adapter both satisfy this contract.
65
+ */
66
+ export interface AtlasRasterPipeline {
67
+ ensureAlpha(): AtlasRasterPipeline;
68
+ resize(options: { width: number; height: number; fit?: 'fill' }): AtlasRasterPipeline;
69
+ raw(): AtlasRasterPipeline;
70
+ extract(options: { left: number; top: number; width: number; height: number }): AtlasRasterPipeline;
71
+ png(): AtlasRasterPipeline;
72
+ rotate(angle: number): AtlasRasterPipeline;
73
+ composite(inputs: AtlasRasterCompositeInput[]): AtlasRasterPipeline;
74
+ metadata(): Promise<AtlasRasterMetadata>;
75
+ toBuffer(options: { resolveWithObject: true }): Promise<AtlasRasterResolvedBuffer>;
76
+ toBuffer(options?: { resolveWithObject?: false }): Promise<Uint8Array>;
77
+ toFile(path: string): Promise<unknown>;
78
+ }
79
+
80
+ export type AtlasRasterBackend = (input: AtlasRasterInput) => AtlasRasterPipeline;
package/src/publish.ts CHANGED
@@ -18,12 +18,12 @@ import {
18
18
  import { createTransform } from './utils.js';
19
19
  import { atlas, type AtlasOptions } from './atlas.js';
20
20
  import { publishCodeGeneration, resolveProjectBasePath } from './codegen.js';
21
- import { formatPluginError, loadPlugins, type LoadedPlugin } from './plugins/loader.js';
21
+ import { formatPluginError, type LoadedPlugin } from './plugins/types.js';
22
+ import type { AtlasRasterBackend, PublishFileSystem } from './publish/contracts.js';
22
23
  import type {
23
24
  CliPublishSettings,
24
25
  HasOptionalFont,
25
26
  PackagePublishArtifactsExtras,
26
- PublishFileSystem,
27
27
  RootProjectSettings,
28
28
  } from './shared-types.js';
29
29
 
@@ -46,10 +46,10 @@ export interface PublishOptions {
46
46
  fileExtension?: string;
47
47
 
48
48
  /**
49
- * Sharp module instance for atlas image compositing.
49
+ * Raster backend for atlas image compositing.
50
50
  * If not provided, atlas packing only computes layout (no PNGs generated).
51
51
  */
52
- encoder?: unknown;
52
+ encoder?: AtlasRasterBackend;
53
53
 
54
54
  /**
55
55
  * Base path for reading source images (project assets root).
@@ -79,9 +79,33 @@ export interface PublishOptions {
79
79
  * Empty or omitted means publishing the main branch.
80
80
  */
81
81
  branch?: string;
82
- }
83
82
 
84
- export interface ResolvedPublishAtlasOptions extends Pick<AtlasOptions, 'maxSize' | 'fast' | 'allowRotation' | 'padding' | 'powerOfTwo' | 'square' | 'multiPage' | 'trimImage' | 'extractAlpha'> {}
83
+ /**
84
+ * Publish hooks supplied by the host adapter.
85
+ *
86
+ * Node adapters load project plugins. Browser adapters pass an empty list.
87
+ */
88
+ plugins?: LoadedPlugin[];
89
+
90
+ /**
91
+ * Run generic code generation after runtime artifacts. Default: true.
92
+ */
93
+ codeGeneration?: boolean;
94
+ }
95
+
96
+ export interface ResolvedPublishAtlasOptions
97
+ extends Pick<
98
+ AtlasOptions,
99
+ | 'maxSize'
100
+ | 'fast'
101
+ | 'allowRotation'
102
+ | 'padding'
103
+ | 'powerOfTwo'
104
+ | 'square'
105
+ | 'multiPage'
106
+ | 'trimImage'
107
+ | 'extractAlpha'
108
+ > {}
85
109
 
86
110
  export interface ResolvePublishOptionsOverrides {
87
111
  compressed?: boolean;
@@ -139,16 +163,6 @@ async function runPublishPluginHook(
139
163
  }
140
164
  }
141
165
 
142
- function resolvePublishPluginsDir(doc: Document, options: PublishOptions): string {
143
- const fs = options.fs;
144
- const projectDir = doc.getProjectDir?.() ?? '';
145
- if (projectDir) return fs?.join ? fs.join(projectDir, 'plugins') : `${projectDir.replace(/[/\\]+$/, '')}/plugins`;
146
-
147
- const projectBasePath = resolveProjectBasePath(options.basePath);
148
- if (!projectBasePath) return '';
149
- return fs?.join ? fs.join(projectBasePath, 'plugins') : `${projectBasePath.replace(/[/\\]+$/, '')}/plugins`;
150
- }
151
-
152
166
  interface ImageResourceExtras extends Record<string, unknown> {
153
167
  _fileName?: string;
154
168
  }
@@ -229,27 +243,6 @@ interface ComponentWithPublishRefs {
229
243
  listTransitions?(): TransitionWithPublishRefs[];
230
244
  }
231
245
 
232
- interface PublishEncoderMetadata {
233
- width?: number;
234
- height?: number;
235
- channels?: number;
236
- }
237
-
238
- interface PublishEncoderResolvedBuffer {
239
- data: Uint8Array;
240
- info: Required<Pick<PublishEncoderMetadata, 'width' | 'height' | 'channels'>> & PublishEncoderMetadata;
241
- }
242
-
243
- interface PublishEncoderPipeline {
244
- ensureAlpha(): PublishEncoderPipeline;
245
- resize(options: { width: number; height: number; fit: 'fill' }): PublishEncoderPipeline;
246
- raw(): PublishEncoderPipeline;
247
- toBuffer(options: { resolveWithObject: true }): Promise<PublishEncoderResolvedBuffer>;
248
- metadata(): Promise<PublishEncoderMetadata>;
249
- }
250
-
251
- type PublishEncoder = (input: string | Uint8Array) => PublishEncoderPipeline;
252
-
253
246
  const UNITY_PROJECT_TYPE = ProjectType.Unity;
254
247
  const COCOS_CREATOR_PROJECT_TYPE = ProjectType.CocosCreator;
255
248
 
@@ -302,8 +295,7 @@ export function resolvePublishOptions(
302
295
  const atlasSetting = publishSettings.atlasSetting ?? {};
303
296
  const projectType = root.getProjectType();
304
297
 
305
- const fileExtension = overrides.fileExtension
306
- ?? resolveDefaultPublishFileExtension(projectType, publishSettings);
298
+ const fileExtension = overrides.fileExtension ?? resolveDefaultPublishFileExtension(projectType, publishSettings);
307
299
 
308
300
  let compressed = overrides.compressed ?? publishSettings.compressDesc ?? false;
309
301
  if (projectType === UNITY_PROJECT_TYPE) {
@@ -347,7 +339,6 @@ function joinPathSegments(left: string, right: string): string {
347
339
  return `${normalizedLeft}${separator}${normalizedRight}`;
348
340
  }
349
341
 
350
-
351
342
  function dirname(filePath: string): string {
352
343
  const trimmed = filePath.replace(/[/\\]+$/, '');
353
344
  const match = trimmed.match(/^(.*)[/\\][^/\\]+$/);
@@ -386,7 +377,9 @@ function isMovieClipResource(resource: ReturnType<Package['listResources']>[numb
386
377
  return resource.propertyType === 'MovieClipResource';
387
378
  }
388
379
 
389
- function isHighResolutionResource(resource: ReturnType<Package['listResources']>[number]): resource is ImageResource | MovieClipResource {
380
+ function isHighResolutionResource(
381
+ resource: ReturnType<Package['listResources']>[number],
382
+ ): resource is ImageResource | MovieClipResource {
390
383
  return isImageResource(resource) || isMovieClipResource(resource);
391
384
  }
392
385
 
@@ -406,7 +399,9 @@ function isSpineResource(resource: ReturnType<Package['listResources']>[number])
406
399
  return resource.propertyType === 'SpineResource';
407
400
  }
408
401
 
409
- function isDragonBonesResource(resource: ReturnType<Package['listResources']>[number]): resource is DragonBonesResource {
402
+ function isDragonBonesResource(
403
+ resource: ReturnType<Package['listResources']>[number],
404
+ ): resource is DragonBonesResource {
410
405
  return resource.propertyType === 'DragonBonesResource';
411
406
  }
412
407
 
@@ -453,10 +448,7 @@ function addLocalFontRef(target: Set<string>, pkgId: string, value: string | str
453
448
  addLocalUiResourceRef(target, pkgId, value ?? undefined);
454
449
  }
455
450
 
456
- function resolvePackageAssetsBasePath(
457
- basePath: string,
458
- resource: BranchAwarePublishedResource | undefined,
459
- ): string {
451
+ function resolvePackageAssetsBasePath(basePath: string, resource: BranchAwarePublishedResource | undefined): string {
460
452
  const branchName = resource?.getBranch?.() ?? '';
461
453
  if (!branchName) return basePath;
462
454
  const normalized = basePath.replace(/[/\\]+$/, '');
@@ -510,7 +502,11 @@ function resolvePublishedMiscFileName(resource: MiscResource, projectType: numbe
510
502
  }
511
503
 
512
504
  function resolvePublishedSkeletonFileName(resource: SpineResource | DragonBonesResource, projectType: number): string {
513
- if (projectType === UNITY_PROJECT_TYPE && isSpineResource(resource) && resource.getFile().toLowerCase().endsWith('.skel')) {
505
+ if (
506
+ projectType === UNITY_PROJECT_TYPE &&
507
+ isSpineResource(resource) &&
508
+ resource.getFile().toLowerCase().endsWith('.skel')
509
+ ) {
514
510
  return `${resource.getFile()}.bytes`;
515
511
  }
516
512
  return resource.getFile();
@@ -528,7 +524,11 @@ function setPublishedFileExtra(
528
524
  }
529
525
 
530
526
  function setPublishedIdExtra(
531
- resource: { getId(): string; getExtras(): Record<string, unknown> | undefined; setExtras(value: Record<string, unknown>): unknown },
527
+ resource: {
528
+ getId(): string;
529
+ getExtras(): Record<string, unknown> | undefined;
530
+ setExtras(value: Record<string, unknown>): unknown;
531
+ },
532
532
  effectiveId: string | null,
533
533
  ): void {
534
534
  const extras = (resource.getExtras() as PublishFileExtras | undefined) ?? {};
@@ -553,11 +553,7 @@ function getBranchName(resource: BranchAwarePublishedResource | undefined): stri
553
553
  return resource?.getBranch?.() ?? '';
554
554
  }
555
555
 
556
- function buildBranchResourceKey(resource: {
557
- propertyType: string;
558
- getPath(): string;
559
- getName(): string;
560
- }): string {
556
+ function buildBranchResourceKey(resource: { propertyType: string; getPath(): string; getName(): string }): string {
561
557
  return `${resource.propertyType}|${resource.getPath() ?? ''}|${resource.getName() ?? ''}`;
562
558
  }
563
559
 
@@ -567,12 +563,15 @@ const HIGH_RESOLUTION_LEVELS = [
567
563
  { scale: 4, bit: 4, slot: 2 },
568
564
  ] as const;
569
565
 
570
- function buildHighResolutionResourceKey(resource: {
571
- propertyType: string;
572
- getPath(): string;
573
- getName(): string;
574
- getBranch?(): string;
575
- }, name = resource.getName()): string {
566
+ function buildHighResolutionResourceKey(
567
+ resource: {
568
+ propertyType: string;
569
+ getPath(): string;
570
+ getName(): string;
571
+ getBranch?(): string;
572
+ },
573
+ name = resource.getName(),
574
+ ): string {
576
575
  return `${resource.propertyType}|${resource.getBranch?.() ?? ''}|${resource.getPath() ?? ''}|${name}`;
577
576
  }
578
577
 
@@ -622,7 +621,10 @@ function collectHighResolutionItemIds(
622
621
  }
623
622
 
624
623
  const highResolutionResource = highResolutionResourceByKey.get(
625
- buildHighResolutionResourceKey(resource, appendHighResolutionScaleToName(resource.getName(), level.scale)),
624
+ buildHighResolutionResourceKey(
625
+ resource,
626
+ appendHighResolutionScaleToName(resource.getName(), level.scale),
627
+ ),
626
628
  );
627
629
  if (!highResolutionResource) {
628
630
  ids[level.slot] = null;
@@ -767,7 +769,12 @@ function collectPackagePublishContext(
767
769
  continue;
768
770
  }
769
771
  if (isImageResource(resource)) {
770
- if (resource.getExported() || referencedIds.has(resourceId) || spriteItemIds.has(resourceId) || pixelHitTestImageIds.has(resourceId)) {
772
+ if (
773
+ resource.getExported() ||
774
+ referencedIds.has(resourceId) ||
775
+ spriteItemIds.has(resourceId) ||
776
+ pixelHitTestImageIds.has(resourceId)
777
+ ) {
771
778
  publishedResourceIds.add(resourceId);
772
779
  }
773
780
  continue;
@@ -888,7 +895,7 @@ async function applyPixelHitTests(
888
895
  pkg: Package,
889
896
  imageIds: Set<string>,
890
897
  basePath: string | undefined,
891
- encoder: PublishEncoder | undefined,
898
+ encoder: AtlasRasterBackend | undefined,
892
899
  ): Promise<void> {
893
900
  const images = pkg.listImageResources();
894
901
  for (const image of images) {
@@ -950,7 +957,7 @@ async function applyPixelHitTests(
950
957
  async function annotatePackagePublishArtifacts(
951
958
  pkg: Package,
952
959
  basePath: string | undefined,
953
- encoder: PublishEncoder | undefined,
960
+ encoder: AtlasRasterBackend | undefined,
954
961
  options: {
955
962
  projectType: number;
956
963
  includeBranches: boolean;
@@ -1002,10 +1009,7 @@ function getAnnotatedExportedResourceIds(pkg: Package): Set<string> {
1002
1009
  return new Set(extras.exportedResourceIds ?? []);
1003
1010
  }
1004
1011
 
1005
- function getPublishedSkeletonDependencyImageIds(
1006
- pkg: Package,
1007
- publishedResourceIds: Set<string>,
1008
- ): Set<string> {
1012
+ function getPublishedSkeletonDependencyImageIds(pkg: Package, publishedResourceIds: Set<string>): Set<string> {
1009
1013
  const imageIds = new Set<string>();
1010
1014
  const resourcesById = new Map(pkg.listResources().map((resource) => [resource.getId(), resource] as const));
1011
1015
  for (const resource of pkg.listResources()) {
@@ -1035,7 +1039,9 @@ async function exportPackageSounds(
1035
1039
  return isSoundResource(resource) && publishedResourceIds.has(resource.getId());
1036
1040
  });
1037
1041
  if (hasPublishedSound) {
1038
- logger.warn(`publish: Sound resources in package "${pkg.getName()}" were not exported because basePath/readFileRaw is unavailable.`);
1042
+ logger.warn(
1043
+ `publish: Sound resources in package "${pkg.getName()}" were not exported because basePath/readFileRaw is unavailable.`,
1044
+ );
1039
1045
  }
1040
1046
  return;
1041
1047
  }
@@ -1071,19 +1077,23 @@ async function exportPackageExternalResources(
1071
1077
  if (!basePath || !readFileRaw) {
1072
1078
  const hasPublishedExternal = pkg.listResources().some((resource) => {
1073
1079
  return (
1074
- (isMiscResource(resource) || isSkeletonResource(resource))
1075
- && exportedResourceIds.has(resource.getId())
1076
- ) || skeletonDependencyImageIds.has(resource.getId());
1080
+ ((isMiscResource(resource) || isSkeletonResource(resource)) &&
1081
+ exportedResourceIds.has(resource.getId())) ||
1082
+ skeletonDependencyImageIds.has(resource.getId())
1083
+ );
1077
1084
  });
1078
1085
  if (hasPublishedExternal) {
1079
- logger.warn(`publish: External resources in package "${pkg.getName()}" were not exported because basePath/readFileRaw is unavailable.`);
1086
+ logger.warn(
1087
+ `publish: External resources in package "${pkg.getName()}" were not exported because basePath/readFileRaw is unavailable.`,
1088
+ );
1080
1089
  }
1081
1090
  return;
1082
1091
  }
1083
1092
 
1084
1093
  for (const resource of pkg.listResources()) {
1085
1094
  const resourceId = resource.getId();
1086
- const isSkeletonExternal = exportedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
1095
+ const isSkeletonExternal =
1096
+ exportedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
1087
1097
  const isSkeletonImageDependency = skeletonDependencyImageIds.has(resourceId) && isImageResource(resource);
1088
1098
  if (!isSkeletonExternal && !isSkeletonImageDependency) continue;
1089
1099
 
@@ -1094,7 +1104,8 @@ async function exportPackageExternalResources(
1094
1104
  targetName = resolveImageFileName(resource);
1095
1105
  } else if (isMiscResource(resource) || isSkeletonResource(resource)) {
1096
1106
  sourcePath = resolveGenericResourcePath(resource, pkg, basePath);
1097
- targetName = ((resource.getExtras() as PublishFileExtras | undefined) ?? {})._publishedFile ?? resource.getFile();
1107
+ targetName =
1108
+ ((resource.getExtras() as PublishFileExtras | undefined) ?? {})._publishedFile ?? resource.getFile();
1098
1109
  } else {
1099
1110
  continue;
1100
1111
  }
@@ -1104,7 +1115,9 @@ async function exportPackageExternalResources(
1104
1115
  const data = await readFileRaw(sourcePath);
1105
1116
  await fs.writeFileRaw(targetPath, data);
1106
1117
  } catch {
1107
- logger.warn(`publish: Could not export external resource "${resource.getId()}" from package "${pkg.getName()}".`);
1118
+ logger.warn(
1119
+ `publish: Could not export external resource "${resource.getId()}" from package "${pkg.getName()}".`,
1120
+ );
1108
1121
  }
1109
1122
  }
1110
1123
  }
@@ -1113,10 +1126,13 @@ async function exportPackageExternalResources(
1113
1126
  * Publishes a FairyGUI project.
1114
1127
  *
1115
1128
  * Orchestrates:
1116
- * 1. Atlas packing (MaxRects layout + optional sharp compositing)
1129
+ * 1. Atlas packing (MaxRects layout + optional raster compositing)
1117
1130
  * 2. Per-package .fui binary serialization
1118
1131
  * 3. File writing to the output directory
1119
1132
  *
1133
+ * This is the capability-injected core. Standard hosts should use
1134
+ * `publishNode()` or `publishBrowser()` through their dedicated entries.
1135
+ *
1120
1136
  * ```ts
1121
1137
  * import sharp from 'sharp';
1122
1138
  * const io = new NodeIO();
@@ -1140,7 +1156,9 @@ export function publish(options: PublishOptions): Transform {
1140
1156
  if (isAbsolutePathLike(trimmed) || !projectBasePath) {
1141
1157
  return trimTrailingSlashes(trimmed);
1142
1158
  }
1143
- return trimTrailingSlashes(options.fs ? options.fs.join(projectBasePath, trimmed) : joinPathSegments(projectBasePath, trimmed));
1159
+ return trimTrailingSlashes(
1160
+ options.fs ? options.fs.join(projectBasePath, trimmed) : joinPathSegments(projectBasePath, trimmed),
1161
+ );
1144
1162
  };
1145
1163
 
1146
1164
  const resolveProjectPublishConfig = (): ResolvedProjectPublishConfig => {
@@ -1167,7 +1185,11 @@ export function publish(options: PublishOptions): Transform {
1167
1185
  };
1168
1186
  };
1169
1187
 
1170
- const resolvePackagePublishPlan = (pkg: Package, config: ResolvedProjectPublishConfig, projectBasePath?: string): ResolvedPackagePublishPlan => {
1188
+ const resolvePackagePublishPlan = (
1189
+ pkg: Package,
1190
+ config: ResolvedProjectPublishConfig,
1191
+ projectBasePath?: string,
1192
+ ): ResolvedPackagePublishPlan => {
1171
1193
  let outputDir: string | undefined;
1172
1194
 
1173
1195
  if (options.output) {
@@ -1215,7 +1237,7 @@ export function publish(options: PublishOptions): Transform {
1215
1237
  },
1216
1238
  });
1217
1239
 
1218
- const publishPackage = async ( plan: ResolvedPackagePublishPlan, writerFs: FileSystem, packageIndex: number) => {
1240
+ const publishPackage = async (plan: ResolvedPackagePublishPlan, writerFs: FileSystem, packageIndex: number) => {
1219
1241
  const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(plan.fileExtension);
1220
1242
  await atlas({
1221
1243
  ...plan.atlas,
@@ -1232,7 +1254,9 @@ export function publish(options: PublishOptions): Transform {
1232
1254
 
1233
1255
  if (!options.fs) return;
1234
1256
  if (!plan.outputDir) {
1235
- throw new Error('publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.');
1257
+ throw new Error(
1258
+ 'publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.',
1259
+ );
1236
1260
  }
1237
1261
 
1238
1262
  await options.fs.mkdir(plan.outputDir);
@@ -1268,8 +1292,7 @@ export function publish(options: PublishOptions): Transform {
1268
1292
  const root = doc.getRoot();
1269
1293
  const logger = doc.getLogger();
1270
1294
  const projectBasePath = resolveProjectBasePath(options.basePath) || doc.getProjectDir?.() || '';
1271
- const pluginsDir = resolvePublishPluginsDir(doc, options);
1272
- const plugins = pluginsDir ? await loadPlugins(doc, pluginsDir) : [];
1295
+ const plugins = options.plugins ?? [];
1273
1296
  await runPublishPluginHook(plugins, 'onPublishStart', doc, options);
1274
1297
 
1275
1298
  const resolved = resolveProjectPublishConfig();
@@ -1298,23 +1321,20 @@ export function publish(options: PublishOptions): Transform {
1298
1321
  // Compute dependency list and selected publish artifacts before atlas packing,
1299
1322
  // so merged-branch publishes can pack the overridden resources with main IDs.
1300
1323
  _computeDependencies(doc, pkg, pkgMap);
1301
- await annotatePackagePublishArtifacts(
1302
- pkg,
1303
- options.basePath,
1304
- options.encoder as PublishEncoder | undefined,
1305
- {
1306
- projectType: resolved.projectType,
1307
- includeBranches: resolved.includeBranches,
1308
- activeBranch: resolved.activeBranch,
1309
- includeHighResolution: resolved.includeHighResolution,
1310
- },
1311
- );
1324
+ await annotatePackagePublishArtifacts(pkg, options.basePath, options.encoder, {
1325
+ projectType: resolved.projectType,
1326
+ includeBranches: resolved.includeBranches,
1327
+ activeBranch: resolved.activeBranch,
1328
+ includeHighResolution: resolved.includeHighResolution,
1329
+ });
1312
1330
  }
1313
1331
 
1314
1332
  const plans = allPackages.map((pkg) => resolvePackagePublishPlan(pkg, resolved, projectBasePath));
1315
1333
 
1316
1334
  if (!options.fs) {
1317
- logger.info(`publish: No fs provided — layout computed for ${allPackages.length} package(s), skipping file output.`);
1335
+ logger.info(
1336
+ `publish: No fs provided — layout computed for ${allPackages.length} package(s), skipping file output.`,
1337
+ );
1318
1338
  const noopWriterFs = toBinaryWriterFileSystem(createNoopPublishFs());
1319
1339
  for (const plan of plans) {
1320
1340
  await publishPackage(plan, noopWriterFs, allDocPackages.indexOf(plan.pkg));
@@ -1338,14 +1358,18 @@ export function publish(options: PublishOptions): Transform {
1338
1358
  await publishPackage(plan, writerFs, pkgIndex);
1339
1359
  }
1340
1360
 
1341
- await publishCodeGeneration(doc, {
1342
- basePath: options.basePath,
1343
- fs: options.fs,
1344
- packages: allPackages,
1345
- plugins,
1346
- });
1361
+ if (options.codeGeneration !== false) {
1362
+ await publishCodeGeneration(doc, {
1363
+ basePath: options.basePath,
1364
+ fs: options.fs,
1365
+ packages: allPackages,
1366
+ plugins,
1367
+ });
1368
+ }
1347
1369
 
1348
- const publishedTargets = [...new Set(plans.map((plan) => plan.outputDir).filter((value): value is string => Boolean(value)))];
1370
+ const publishedTargets = [
1371
+ ...new Set(plans.map((plan) => plan.outputDir).filter((value): value is string => Boolean(value))),
1372
+ ];
1349
1373
  logger.info(
1350
1374
  publishedTargets.length > 0
1351
1375
  ? `publish: Published ${allPackages.length} package(s) to ${publishedTargets.join(', ')}`
@@ -1363,7 +1387,12 @@ export function publish(options: PublishOptions): Transform {
1363
1387
  function _computeDependencies(doc: Document, pkg: Package, pkgMap: Map<string, Package>): void {
1364
1388
  const referencedPkgIds = new Set<string>();
1365
1389
  const pkgId = pkg.getId();
1366
- const packageOrder = new Map(doc.getRoot().listPackages().map((entry, index) => [entry.getId(), index] as const));
1390
+ const packageOrder = new Map(
1391
+ doc
1392
+ .getRoot()
1393
+ .listPackages()
1394
+ .map((entry, index) => [entry.getId(), index] as const),
1395
+ );
1367
1396
  const addDependencyPackageId = (dependencyPkgId: string | null | undefined): void => {
1368
1397
  const normalized = dependencyPkgId?.trim() ?? '';
1369
1398
  if (!normalized || normalized === pkgId) return;
@@ -1,4 +1,6 @@
1
- import type { FileSystem, ProjectSettings, PublishSettings } from '@openfairygui/core';
1
+ import type { ProjectSettings, PublishSettings } from '@openfairygui/core';
2
+
3
+ export type { PublishFileSystem } from './publish/contracts.js';
2
4
 
3
5
  export type ExtrasMap = Record<string, unknown>;
4
6
 
@@ -57,10 +59,3 @@ export interface HasOptionalSrc {
57
59
  export interface HasOptionalUrl {
58
60
  getUrl?(): string | undefined;
59
61
  }
60
-
61
- export type PublishFileSystem = Pick<FileSystem, 'join' | 'mkdir' | 'writeFileRaw'> & {
62
- deleteFile?: (path: string) => Promise<void>;
63
- exists?: FileSystem['exists'];
64
- readdir?: FileSystem['readdir'];
65
- readFileRaw?: FileSystem['readFileRaw'];
66
- };
package/src/web.ts ADDED
@@ -0,0 +1,11 @@
1
+ export {
2
+ publishBrowser,
3
+ type BrowserPublishedFile,
4
+ type BrowserPublishAtlasOptions,
5
+ type BrowserPublishDiagnostic,
6
+ type BrowserPublishOptions,
7
+ type BrowserPublishProjectType,
8
+ type BrowserPublishResult,
9
+ type BrowserPublishOutputFileSystem,
10
+ type BrowserPublishSourceFileSystem,
11
+ } from './adapters/web/publish.js';