@dp-bohrium/widget-cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as resolveWidgetProjects, i as checkWidgetProject, n as scaffoldDshWidget, o as compileAuthorManifest, r as buildWidgetPackage, t as auditDshPlugin } from "./dsh-DgkNH7L-.mjs";
1
+ import { c as resolveWidgetProjects, l as compileAuthorManifest, n as scaffoldDshWidget, r as buildWidgetPackage, s as checkWidgetProject, t as auditDshPlugin } from "./dsh-BzxDpEcZ.mjs";
2
2
  //#region src/core.mjs
3
3
  /** Validate a community template before registering it in a custom CLI. */
4
4
  function defineWidgetTemplate(template) {
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { createHash } from "node:crypto";
3
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { existsSync, lstatSync, readFileSync, readdirSync, statSync } from "node:fs";
4
4
  import { lstat, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
5
5
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
@@ -95,52 +95,78 @@ function collectReferencedFileIds(request) {
95
95
  return referenced;
96
96
  }
97
97
  //#endregion
98
- //#region ../widget-project/src/manifest.mjs
99
- const require$2 = createRequire(import.meta.url);
98
+ //#region ../widget-sdk/dist/runtime-RyAEhkyn.js
99
+ /** 从当前安装的 SDK package.json 读取版本,插件作者不需要重复填写。 */
100
+ const WIDGET_SDK_VERSION = "0.4.1";
101
+ //#endregion
102
+ //#region ../widget-sdk/dist/index.js
103
+ function compiledContribution(contribution) {
104
+ switch (contribution.type) {
105
+ case "panel": return {
106
+ type: "panel",
107
+ id: contribution.id,
108
+ ...contribution.placement === void 0 ? {} : { placement: contribution.placement }
109
+ };
110
+ case "file-preview": return {
111
+ type: "file-preview",
112
+ extensions: [...contribution.extensions]
113
+ };
114
+ }
115
+ }
100
116
  /**
101
- * Convert the author-facing VS Code-style plugin manifest to the Host
102
- * manifest consumed by Host Runtime Config. This is deliberately a tooling
103
- * boundary: source code and editor tooling use `contributes`, while only the
104
- * upload/preview pipeline knows about `widgetConfig.contribution` and protocol fields.
117
+ * 将开发者维护的插件清单编译为宿主可加载的宿主清单。
118
+ *
119
+ * 这是构建工具的边界函数,不是类型提示入口:作者直接编辑带 `$schema` 的 JSON,
120
+ * SDK 版本从当前安装包读取,宿主入口默认落到 dist/widget.js,内部 contribution 标识只在这里生成。
105
121
  */
106
- function compileAuthorManifest(manifest, options = {}) {
107
- if (!isAuthorManifest(manifest)) return manifest;
108
- const sdkVersion = options.sdkVersion ?? installedSdkVersion();
122
+ function compileWidgetPluginManifest(manifest, options = {}) {
123
+ const samples = options.samples ?? manifest.samples;
124
+ if (!samples || samples.length === 0) throw new Error("Widget Plugin Manifest must provide at least one sample before packaging");
109
125
  const contribution = manifest.contributes.widget;
110
- if (!contribution || typeof contribution !== "object" || !["panel", "file-preview"].includes(contribution.type) || contribution.type === "file-preview" && !Array.isArray(contribution.extensions)) return manifest;
111
- const compiledContribution = contribution.type === "panel" ? {
112
- type: "panel",
113
- id: contribution.id
114
- } : {
115
- type: "file-preview",
116
- extensions: [...contribution.extensions]
117
- };
126
+ if (contribution.type === "panel" && contribution.placement?.kind === "region" && (manifest.capabilities?.region?.mode !== contribution.placement.mode || !manifest.capabilities.region.regions.includes(contribution.placement.region))) throw new Error("A region Widget contribution must declare a matching region capability");
127
+ const activationEvents = manifest.activationEvents ? [...manifest.activationEvents] : void 0;
118
128
  return {
119
129
  name: manifest.name,
120
130
  ...manifest.description === void 0 ? {} : { description: manifest.description },
121
131
  widgetConfig: {
122
132
  schemaVersion: 1,
123
133
  hostApiVersion: 1,
124
- widgetSdkVersion: sdkVersion,
134
+ widgetSdkVersion: WIDGET_SDK_VERSION,
125
135
  entry: options.hostEntry ?? "dist/widget.js",
126
- contribution: compiledContribution,
127
- ...manifest.activationEvents === void 0 ? {} : { activationEvents: manifest.activationEvents },
136
+ contribution: compiledContribution(manifest.contributes.widget),
137
+ ...activationEvents === void 0 ? {} : { activationEvents },
128
138
  ...manifest.capabilities === void 0 ? {} : { requestedCapabilities: manifest.capabilities },
129
139
  ...manifest.runtime === void 0 ? {} : { runtime: manifest.runtime },
130
- samples: options.samples ?? manifest.samples ?? []
140
+ samples
131
141
  }
132
142
  };
133
143
  }
144
+ //#endregion
145
+ //#region ../widget-project/src/manifest.mjs
146
+ /**
147
+ * Convert the author-facing VS Code-style plugin manifest to the Host
148
+ * manifest consumed by Host Runtime Config. This is deliberately a tooling
149
+ * boundary: source code and editor tooling use `contributes`, while only the
150
+ * upload/preview pipeline knows about `widgetConfig.contribution` and protocol fields.
151
+ */
152
+ function compileAuthorManifest(manifest, options = {}) {
153
+ if (!isAuthorManifest(manifest)) return manifest;
154
+ const contribution = manifest.contributes?.widget;
155
+ if (!contribution || typeof contribution !== "object" || !["panel", "file-preview"].includes(contribution.type) || contribution.type === "file-preview" && !Array.isArray(contribution.extensions)) return manifest;
156
+ const compiled = (options.compiler ?? compileWidgetPluginManifest)(manifest, {
157
+ hostEntry: options.hostEntry,
158
+ samples: options.samples
159
+ });
160
+ if (options.sdkVersion !== void 0 && compiled.widgetConfig.widgetSdkVersion !== options.sdkVersion) {
161
+ const error = /* @__PURE__ */ new Error(`Manifest compiler SDK version ${compiled.widgetConfig.widgetSdkVersion} does not match requested SDK version ${options.sdkVersion}`);
162
+ error.code = "WIDGET_CLI_PROJECT_SDK_MISMATCH";
163
+ throw error;
164
+ }
165
+ return compiled;
166
+ }
134
167
  function isAuthorManifest(value) {
135
168
  return Boolean(value && typeof value === "object" && !Array.isArray(value) && "contributes" in value && "version" in value);
136
169
  }
137
- function installedSdkVersion() {
138
- try {
139
- return require$2("@dp-bohrium/widget-sdk/package.json").version;
140
- } catch {
141
- return "0.0.0-dev";
142
- }
143
- }
144
170
  //#endregion
145
171
  //#region ../../node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
146
172
  var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -6798,7 +6824,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
6798
6824
  exports.default = addMetaSchema2020;
6799
6825
  }));
6800
6826
  //#endregion
6801
- //#region ../contract-testing/dist/manifest-validator-BWdmIEES.js
6827
+ //#region ../contract-testing/dist/manifest-validator-DCdGSuVM.js
6802
6828
  var import__2020 = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
6803
6829
  Object.defineProperty(exports, "__esModule", { value: true });
6804
6830
  exports.MissingRefError = exports.ValidationError = exports.CodeGen = exports.Name = exports.nil = exports.stringify = exports.str = exports._ = exports.KeywordCxt = exports.Ajv2020 = void 0;
@@ -6978,7 +7004,7 @@ const widgetManifestSchema = {
6978
7004
  }
6979
7005
  },
6980
7006
  "FilesCapabilityRequest": {
6981
- "description": "只读文件能力的申请开关;对应 files.list / files.metadata / files.read / files.download。",
7007
+ "description": "文件能力的独立申请开关;保存已有文件必须单独申请 files.write 并获得 Host 授权,可写范围由 Host grant 决定。",
6982
7008
  "title": "FilesCapabilityRequest",
6983
7009
  "type": "object",
6984
7010
  "additionalProperties": false,
@@ -6986,7 +7012,8 @@ const widgetManifestSchema = {
6986
7012
  "list": { "type": "boolean" },
6987
7013
  "metadata": { "type": "boolean" },
6988
7014
  "read": { "type": "boolean" },
6989
- "download": { "type": "boolean" }
7015
+ "download": { "type": "boolean" },
7016
+ "write": { "type": "boolean" }
6990
7017
  }
6991
7018
  },
6992
7019
  "EventsCapabilityRequest": {
@@ -7439,6 +7466,8 @@ const widgetRuntimeContractsSchema = {
7439
7466
  "presentationCapabilityResult": { "$ref": "#/$defs/PresentationCapabilityResult" },
7440
7467
  "regionCapabilityResult": { "$ref": "#/$defs/RegionCapabilityResult" },
7441
7468
  "fileDownloadResult": { "$ref": "#/$defs/FileDownloadResult" },
7469
+ "fileReadResult": { "$ref": "#/$defs/FileReadResult" },
7470
+ "fileWriteResult": { "$ref": "#/$defs/FileWriteResult" },
7442
7471
  "taskObservationSnapshot": { "$ref": "#/$defs/TaskObservationSnapshot" },
7443
7472
  "taskObservationEvent": { "$ref": "#/$defs/TaskObservationEvent" },
7444
7473
  "taskObservationBatch": { "$ref": "#/$defs/TaskObservationBatch" },
@@ -7671,7 +7700,7 @@ const widgetRuntimeContractsSchema = {
7671
7700
  }
7672
7701
  },
7673
7702
  "FileReference": {
7674
- "description": "只读文件引用;仅含不透明 id 与展示元数据,不含路径或 URL。",
7703
+ "description": "Host 签发的文件引用;仅含不透明 id、可选内容版本和展示元数据。引用本身不授予写权限,不含路径或 URL。",
7675
7704
  "title": "FileReference",
7676
7705
  "type": "object",
7677
7706
  "additionalProperties": false,
@@ -7703,7 +7732,121 @@ const widgetRuntimeContractsSchema = {
7703
7732
  "size": {
7704
7733
  "type": "integer",
7705
7734
  "minimum": 0
7706
- }
7735
+ },
7736
+ "revision": { "$ref": "#/$defs/FileRevision" }
7737
+ }
7738
+ },
7739
+ "FileRevision": {
7740
+ "description": "由存储执行端签发的文件内容版本,用于条件读取和保存;不是 RenderRequest.revision。",
7741
+ "title": "FileRevision",
7742
+ "type": "string",
7743
+ "minLength": 1,
7744
+ "maxLength": 256
7745
+ },
7746
+ "FileMutationId": {
7747
+ "description": "一次逻辑保存的稳定标识;重试或确认结果时必须复用,相同标识不得提交不同内容。",
7748
+ "title": "FileMutationId",
7749
+ "type": "string",
7750
+ "minLength": 1,
7751
+ "maxLength": 128,
7752
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$"
7753
+ },
7754
+ "FileReadInput": {
7755
+ "description": "分块读取已授权文件;后续分块应携带首块返回的 expectedRevision,防止拼接不同版本。",
7756
+ "title": "FileReadInput",
7757
+ "type": "object",
7758
+ "additionalProperties": false,
7759
+ "required": [
7760
+ "file",
7761
+ "offset",
7762
+ "length",
7763
+ "encoding"
7764
+ ],
7765
+ "properties": {
7766
+ "file": { "$ref": "#/$defs/FileReference" },
7767
+ "offset": {
7768
+ "type": "integer",
7769
+ "minimum": 0,
7770
+ "maximum": 9007199254740991
7771
+ },
7772
+ "length": {
7773
+ "type": "integer",
7774
+ "minimum": 1,
7775
+ "maximum": 9007199254740991
7776
+ },
7777
+ "encoding": { "enum": ["utf8", "base64"] },
7778
+ "expectedRevision": { "$ref": "#/$defs/FileRevision" }
7779
+ }
7780
+ },
7781
+ "FileReadResult": {
7782
+ "description": "文件读取结果;普通读取只要求内容与编码,原有游标和截断信息按 Host 能力返回。revision 可选且必须与本次读取的内容属于同一快照;请求 expectedRevision 时必须返回匹配版本。缺少 revision 的结果仅用于只读展示,不能据此条件保存。提供字节游标时 nextOffset 等于请求 offset + bytesRead,不按字符串长度计数。",
7783
+ "title": "FileReadResult",
7784
+ "type": "object",
7785
+ "additionalProperties": false,
7786
+ "required": ["content", "encoding"],
7787
+ "properties": {
7788
+ "content": { "type": "string" },
7789
+ "encoding": { "enum": ["utf8", "base64"] },
7790
+ "offset": {
7791
+ "type": "integer",
7792
+ "minimum": 0,
7793
+ "maximum": 9007199254740991
7794
+ },
7795
+ "bytesRead": {
7796
+ "type": "integer",
7797
+ "minimum": 0,
7798
+ "maximum": 9007199254740991
7799
+ },
7800
+ "nextOffset": {
7801
+ "type": "integer",
7802
+ "minimum": 0,
7803
+ "maximum": 9007199254740991
7804
+ },
7805
+ "eof": { "type": "boolean" },
7806
+ "truncated": { "type": "boolean" },
7807
+ "revision": { "$ref": "#/$defs/FileRevision" }
7808
+ }
7809
+ },
7810
+ "FileWriteInput": {
7811
+ "description": "按 Host 授权范围对已有文件作整文件条件替换;Host 校验引用、作用域、权限和实际字节预算。超时不表示未写入,使用同一 mutationId 核对结果。",
7812
+ "title": "FileWriteInput",
7813
+ "type": "object",
7814
+ "additionalProperties": false,
7815
+ "required": [
7816
+ "file",
7817
+ "content",
7818
+ "encoding",
7819
+ "expectedRevision",
7820
+ "mutationId"
7821
+ ],
7822
+ "properties": {
7823
+ "file": { "$ref": "#/$defs/FileReference" },
7824
+ "content": { "type": "string" },
7825
+ "encoding": { "enum": ["utf8", "base64"] },
7826
+ "expectedRevision": { "$ref": "#/$defs/FileRevision" },
7827
+ "mutationId": { "$ref": "#/$defs/FileMutationId" }
7828
+ }
7829
+ },
7830
+ "FileWriteResult": {
7831
+ "description": "执行端已确认持久化的保存回执;file.id 保持原文件引用身份,revision 是新内容版本。",
7832
+ "title": "FileWriteResult",
7833
+ "type": "object",
7834
+ "additionalProperties": false,
7835
+ "required": [
7836
+ "file",
7837
+ "revision",
7838
+ "bytesWritten",
7839
+ "mutationId"
7840
+ ],
7841
+ "properties": {
7842
+ "file": { "$ref": "#/$defs/FileReference" },
7843
+ "revision": { "$ref": "#/$defs/FileRevision" },
7844
+ "bytesWritten": {
7845
+ "type": "integer",
7846
+ "minimum": 0,
7847
+ "maximum": 9007199254740991
7848
+ },
7849
+ "mutationId": { "$ref": "#/$defs/FileMutationId" }
7707
7850
  }
7708
7851
  },
7709
7852
  "FileDownloadResult": {
@@ -8119,6 +8262,40 @@ const widgetRuntimeContractsSchema = {
8119
8262
  }
8120
8263
  }
8121
8264
  },
8265
+ {
8266
+ "type": "object",
8267
+ "additionalProperties": false,
8268
+ "required": ["name", "limits"],
8269
+ "properties": {
8270
+ "name": { "const": "files.write" },
8271
+ "limits": {
8272
+ "type": "object",
8273
+ "additionalProperties": false,
8274
+ "required": [
8275
+ "bytesPerRequest",
8276
+ "bytesPerRun",
8277
+ "callsPerMinute"
8278
+ ],
8279
+ "properties": {
8280
+ "bytesPerRequest": {
8281
+ "type": "integer",
8282
+ "minimum": 1,
8283
+ "maximum": 9007199254740991
8284
+ },
8285
+ "bytesPerRun": {
8286
+ "type": "integer",
8287
+ "minimum": 1,
8288
+ "maximum": 9007199254740991
8289
+ },
8290
+ "callsPerMinute": {
8291
+ "type": "integer",
8292
+ "minimum": 1,
8293
+ "maximum": 9007199254740991
8294
+ }
8295
+ }
8296
+ }
8297
+ }
8298
+ },
8122
8299
  {
8123
8300
  "type": "object",
8124
8301
  "additionalProperties": false,
@@ -8620,28 +8797,16 @@ const widgetRuntimeContractsSchema = {
8620
8797
  "required": ["operation", "input"],
8621
8798
  "properties": {
8622
8799
  "operation": { "const": "files.read" },
8623
- "input": {
8624
- "type": "object",
8625
- "additionalProperties": false,
8626
- "required": [
8627
- "file",
8628
- "offset",
8629
- "length",
8630
- "encoding"
8631
- ],
8632
- "properties": {
8633
- "file": { "$ref": "#/$defs/FileReference" },
8634
- "offset": {
8635
- "type": "integer",
8636
- "minimum": 0
8637
- },
8638
- "length": {
8639
- "type": "integer",
8640
- "minimum": 1
8641
- },
8642
- "encoding": { "enum": ["utf8", "base64"] }
8643
- }
8644
- }
8800
+ "input": { "$ref": "#/$defs/FileReadInput" }
8801
+ }
8802
+ },
8803
+ {
8804
+ "type": "object",
8805
+ "additionalProperties": false,
8806
+ "required": ["operation", "input"],
8807
+ "properties": {
8808
+ "operation": { "const": "files.write" },
8809
+ "input": { "$ref": "#/$defs/FileWriteInput" }
8645
8810
  }
8646
8811
  },
8647
8812
  {
@@ -9201,7 +9366,10 @@ const widgetRuntimeContractsSchema = {
9201
9366
  "network-credential-unavailable",
9202
9367
  "network-scope-invalid",
9203
9368
  "network-dns-denied",
9204
- "network-error"
9369
+ "network-error",
9370
+ "revision-conflict",
9371
+ "mutation-conflict",
9372
+ "write-outcome-unknown"
9205
9373
  ] },
9206
9374
  "message": {
9207
9375
  "type": "string",
@@ -9360,6 +9528,18 @@ const widgetRuntimeContractsSchema = {
9360
9528
  "listPageSize": {
9361
9529
  "type": "integer",
9362
9530
  "minimum": 1
9531
+ },
9532
+ "writeBytesPerRequest": {
9533
+ "description": "写入单次原始字节上限;未签发时不得授权 files.write。",
9534
+ "type": "integer",
9535
+ "minimum": 1,
9536
+ "maximum": 9007199254740991
9537
+ },
9538
+ "writeBytesPerRun": {
9539
+ "description": "当前运行的累计写入原始字节上限;未签发时不得授权 files.write。",
9540
+ "type": "integer",
9541
+ "minimum": 1,
9542
+ "maximum": 9007199254740991
9363
9543
  }
9364
9544
  }
9365
9545
  },
@@ -10735,14 +10915,51 @@ async function checkWidgetProject(projectRoot, options = {}) {
10735
10915
  projectRoot: root
10736
10916
  };
10737
10917
  }
10738
- const { validateWidgetManifest, validateRenderRequest, validateCapabilityResult } = await loadContractValidators();
10918
+ const toolchain = options.toolchain ?? {};
10919
+ const { validateWidgetManifest, validateRenderRequest, validateCapabilityResult } = options.validators ?? toolchain.validators ?? await loadContractValidators();
10739
10920
  const sourceManifest = manifest;
10740
- manifest = compileAuthorManifest(manifest, { hostEntry: options.hostEntry });
10921
+ const sdkVersion = options.sdkVersion ?? toolchain.sdkVersion;
10922
+ try {
10923
+ manifest = compileAuthorManifest(manifest, {
10924
+ hostEntry: options.hostEntry,
10925
+ samples: options.samples,
10926
+ sdkVersion,
10927
+ compiler: options.compiler ?? toolchain.compiler
10928
+ });
10929
+ } catch (error) {
10930
+ checks.push({
10931
+ name: "manifest 编译",
10932
+ ok: false
10933
+ });
10934
+ errors.push(`manifest compilation failed: ${error instanceof Error ? error.message : String(error)}`);
10935
+ return {
10936
+ ok: false,
10937
+ label,
10938
+ checks,
10939
+ errors,
10940
+ manifest: sourceManifest,
10941
+ sourceManifest,
10942
+ manifestKind: isAuthorManifest(sourceManifest) ? "author" : "host",
10943
+ sampleRequest: null,
10944
+ sampleRequests: [],
10945
+ fixtures: null,
10946
+ fixturesPath: null,
10947
+ entryPath: null,
10948
+ projectRoot: root
10949
+ };
10950
+ }
10741
10951
  const manifestResult = validateWidgetManifest(manifest);
10742
10952
  checks.push({
10743
10953
  name: "manifest 契约校验 (AJV)",
10744
10954
  ok: manifestResult.ok
10745
10955
  });
10956
+ if (manifestResult.ok && sdkVersion !== void 0 && manifest.widgetConfig.widgetSdkVersion !== sdkVersion) {
10957
+ checks.push({
10958
+ name: "manifest 工具链 SDK 版本",
10959
+ ok: false
10960
+ });
10961
+ errors.push(`manifest SDK version ${manifest.widgetConfig.widgetSdkVersion} does not match toolchain SDK version ${sdkVersion}`);
10962
+ }
10746
10963
  if (!manifestResult.ok) {
10747
10964
  errors.push(...formatContractErrors(manifestResult));
10748
10965
  return {
@@ -10883,8 +11100,29 @@ function resolveWidgetProjects(input, root = repoRoot) {
10883
11100
  throw new Error("请传入 Widget 工程目录、manifest.json 或 all");
10884
11101
  }
10885
11102
  //#endregion
10886
- //#region ../widget-project/src/export.mjs
11103
+ //#region ../widget-project/src/zip.mjs
11104
+ /** 固定归档顺序与时间戳,统一路径安全与重复条目处理。 */
11105
+ function assertArchivePath(path) {
11106
+ if (typeof path !== "string" || path.length === 0 || path.startsWith("/") || path.includes("..") || path.includes("\\") || path.includes("://") || /^[A-Za-z]:/u.test(path) || [...path].some((character) => {
11107
+ const code = character.charCodeAt(0);
11108
+ return code < 32 || code === 127;
11109
+ })) throw new Error(`Invalid archive path: ${path}`);
11110
+ }
11111
+ function normalizeArchivePath(path) {
11112
+ assertArchivePath(path);
11113
+ const normalized = path.split("/").filter((part) => part !== "" && part !== ".").join("/");
11114
+ assertArchivePath(normalized);
11115
+ return normalized;
11116
+ }
10887
11117
  function storedZip(files) {
11118
+ const uniqueFiles = /* @__PURE__ */ new Map();
11119
+ for (const [declaredPath, value] of files) {
11120
+ const path = normalizeArchivePath(declaredPath);
11121
+ const bytes = Buffer.from(value);
11122
+ const previous = uniqueFiles.get(path);
11123
+ if (previous && !previous.equals(bytes)) throw new Error(`Conflicting duplicate archive path: ${path}`);
11124
+ uniqueFiles.set(path, bytes);
11125
+ }
10888
11126
  const table = Array.from({ length: 256 }, (_, index) => {
10889
11127
  let value = index;
10890
11128
  for (let bit = 0; bit < 8; bit++) value = value & 1 ? 3988292384 ^ value >>> 1 : value >>> 1;
@@ -10893,10 +11131,8 @@ function storedZip(files) {
10893
11131
  const locals = [];
10894
11132
  const central = [];
10895
11133
  let offset = 0;
10896
- for (const [path, value] of [...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
10897
- if (!/^[a-zA-Z0-9._/-]+$/.test(path) || path.startsWith("/") || path.split("/").includes("..")) throw new Error("Invalid archive path");
11134
+ for (const [path, bytes] of [...uniqueFiles].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
10898
11135
  const name = Buffer.from(path);
10899
- const bytes = Buffer.from(value);
10900
11136
  let crc = 4294967295;
10901
11137
  for (const byte of bytes) crc = table[(crc ^ byte) & 255] ^ crc >>> 8;
10902
11138
  crc = (crc ^ 4294967295) >>> 0;
@@ -10927,8 +11163,8 @@ function storedZip(files) {
10927
11163
  const centralBytes = Buffer.concat(central);
10928
11164
  const end = Buffer.alloc(22);
10929
11165
  end.writeUInt32LE(101010256);
10930
- end.writeUInt16LE(files.length, 8);
10931
- end.writeUInt16LE(files.length, 10);
11166
+ end.writeUInt16LE(uniqueFiles.size, 8);
11167
+ end.writeUInt16LE(uniqueFiles.size, 10);
10932
11168
  end.writeUInt32LE(centralBytes.length, 12);
10933
11169
  end.writeUInt32LE(offset, 16);
10934
11170
  return Buffer.concat([
@@ -10937,24 +11173,81 @@ function storedZip(files) {
10937
11173
  end
10938
11174
  ]);
10939
11175
  }
10940
- function safeBundlePath(value) {
10941
- return value.replace(/^\/+/u, "");
11176
+ //#endregion
11177
+ //#region ../widget-project/src/export.mjs
11178
+ const EXCLUDED_BUILD_NAMES = /* @__PURE__ */ new Set([
11179
+ "node_modules",
11180
+ ".git",
11181
+ ".widget-build",
11182
+ ".widget-dev",
11183
+ "widget-toolchain.json",
11184
+ ".widget-build-receipt.json"
11185
+ ]);
11186
+ function bundleFileMetadata(root, path) {
11187
+ const normalized = normalizeArchivePath(path);
11188
+ let file = root;
11189
+ let metadata;
11190
+ for (const part of normalized.split("/")) {
11191
+ file = join(file, part);
11192
+ metadata = lstatSync(file);
11193
+ if (metadata.isSymbolicLink()) throw new Error(`ZIP 不允许 symbolic link(符号链接):${path}`);
11194
+ }
11195
+ return {
11196
+ file,
11197
+ metadata
11198
+ };
11199
+ }
11200
+ function readBundleFile(root, path) {
11201
+ const { file, metadata } = bundleFileMetadata(root, path);
11202
+ if (!metadata.isFile()) throw new Error(`ZIP 资源不是普通文件:${path}`);
11203
+ return readFileSync(file);
11204
+ }
11205
+ function collectBuildAssets(root, entry) {
11206
+ entry = normalizeArchivePath(entry);
11207
+ if (!entry.includes("/")) return [[entry, readBundleFile(root, entry)]];
11208
+ const files = [];
11209
+ const rootName = entry.split("/")[0];
11210
+ if (EXCLUDED_BUILD_NAMES.has(rootName)) throw new Error(`构建资源目录不能是工程内部目录:${rootName}`);
11211
+ const visit = (path) => {
11212
+ const { file, metadata } = bundleFileMetadata(root, path);
11213
+ if (metadata.isDirectory()) for (const name of readdirSync(file)) {
11214
+ if (EXCLUDED_BUILD_NAMES.has(name) || /\.zip$/iu.test(name)) continue;
11215
+ visit(`${path}/${name}`);
11216
+ }
11217
+ else files.push([path, readBundleFile(root, path)]);
11218
+ };
11219
+ visit(rootName);
11220
+ return files;
10942
11221
  }
10943
11222
  function buildWidgetPackage(project, options = {}) {
10944
- const manifest = project.manifest ?? compileAuthorManifest(JSON.parse(readFileSync(join(project.projectRoot, "manifest.json"), "utf8")), { hostEntry: options.hostEntry });
11223
+ return storedZip(collectWidgetPackageFiles(project, options));
11224
+ }
11225
+ /** 返回将写入 ZIP 的 `[路径, 字节]` 列表;同一路径可能重复出现,由 `storedZip` 去重。 */
11226
+ function collectWidgetPackageFiles(project, options = {}) {
11227
+ const root = resolve(project.projectRoot);
11228
+ const sourceManifest = project.manifest ?? compileAuthorManifest(JSON.parse(readBundleFile(root, "manifest.json").toString("utf8")), { hostEntry: options.hostEntry });
11229
+ const manifest = {
11230
+ ...sourceManifest,
11231
+ widgetConfig: {
11232
+ ...sourceManifest.widgetConfig,
11233
+ entry: normalizeArchivePath(sourceManifest.widgetConfig.entry),
11234
+ samples: sourceManifest.widgetConfig.samples?.map((sample) => ({
11235
+ ...sample,
11236
+ request: normalizeArchivePath(sample.request)
11237
+ }))
11238
+ }
11239
+ };
10945
11240
  const files = [["manifest.json", Buffer.from(`${JSON.stringify(manifest)}\n`)]];
10946
- const entryPath = join(project.projectRoot, manifest.widgetConfig.entry);
11241
+ const entryPath = join(root, manifest.widgetConfig.entry);
10947
11242
  if (!existsSync(entryPath)) throw new Error("Widget entry is missing; build the project first");
10948
- files.push([safeBundlePath(manifest.widgetConfig.entry), readFileSync(entryPath)]);
10949
- for (const sample of manifest.widgetConfig.samples ?? []) {
10950
- const samplePath = join(project.projectRoot, sample.request);
10951
- if (existsSync(samplePath)) files.push([safeBundlePath(sample.request), readFileSync(samplePath)]);
10952
- }
11243
+ files.push(...collectBuildAssets(root, manifest.widgetConfig.entry));
11244
+ files.push([manifest.widgetConfig.entry, readBundleFile(root, manifest.widgetConfig.entry)]);
11245
+ for (const sample of manifest.widgetConfig.samples ?? []) files.push([sample.request, readBundleFile(root, sample.request)]);
10953
11246
  if (options.includePreviewFixtures) {
10954
- const fixturesPath = join(project.projectRoot, "preview", "fixtures.json");
10955
- if (existsSync(fixturesPath)) files.push(["preview/fixtures.json", readFileSync(fixturesPath)]);
11247
+ const fixturesPath = join(root, "preview", "fixtures.json");
11248
+ if (existsSync(fixturesPath)) files.push(["preview/fixtures.json", readBundleFile(root, "preview/fixtures.json")]);
10956
11249
  }
10957
- return storedZip(files);
11250
+ return files;
10958
11251
  }
10959
11252
  //#endregion
10960
11253
  //#region src/migration/recipes.mjs
@@ -11392,4 +11685,4 @@ async function scaffoldDshWidget(input, output, options) {
11392
11685
  };
11393
11686
  }
11394
11687
  //#endregion
11395
- export { resolveWidgetProjects as a, checkWidgetProject as i, scaffoldDshWidget as n, compileAuthorManifest as o, buildWidgetPackage as r, auditDshPlugin as t };
11688
+ export { normalizeArchivePath as a, resolveWidgetProjects as c, collectWidgetPackageFiles as i, compileAuthorManifest as l, scaffoldDshWidget as n, storedZip as o, buildWidgetPackage as r, checkWidgetProject as s, auditDshPlugin as t };