@syncbridge/common 1.1.4 → 2.0.1

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.
Files changed (163) hide show
  1. package/README.md +192 -1
  2. package/app-factory.d.ts +29 -0
  3. package/{processor-factory.js → app-factory.js} +63 -44
  4. package/classes/{processor-base.d.ts → app-base.d.ts} +3 -3
  5. package/classes/app-base.js +10 -0
  6. package/classes/base-element.d.ts +3 -0
  7. package/classes/base-element.js +2 -1
  8. package/classes/component-base.d.ts +6 -6
  9. package/classes/component-base.js +2 -3
  10. package/classes/console-logger.js +0 -1
  11. package/classes/frame-stream.js +0 -1
  12. package/classes/log-channel-writer.d.ts +32 -0
  13. package/classes/log-channel-writer.js +96 -0
  14. package/classes/runnable.d.ts +6 -0
  15. package/classes/runnable.js +22 -1
  16. package/classes/sb-error.js +0 -1
  17. package/classes/stack-executor.js +0 -1
  18. package/constants.d.ts +1 -1
  19. package/constants.js +1 -2
  20. package/decorators/app-events.decorator.d.ts +5 -0
  21. package/decorators/{processor-events.decorator.js → app-events.decorator.js} +0 -1
  22. package/decorators/app.decorator.d.ts +29 -0
  23. package/decorators/{processor.decorator.js → app.decorator.js} +24 -19
  24. package/decorators/component.decorator.js +13 -6
  25. package/decorators/decorator-helpers.js +0 -1
  26. package/decorators/define-variable.decorator.d.ts +6 -1
  27. package/decorators/define-variable.decorator.js +5 -2
  28. package/decorators/log-channel.decorator.d.ts +97 -0
  29. package/decorators/log-channel.decorator.js +33 -0
  30. package/decorators/use-component.decorator.d.ts +2 -2
  31. package/decorators/use-component.decorator.js +1 -2
  32. package/decorators/use-variables.decorator.d.ts +2 -2
  33. package/decorators/use-variables.decorator.js +0 -1
  34. package/host-link/host-identity.d.ts +53 -0
  35. package/host-link/host-identity.js +139 -0
  36. package/host-link/host-link.d.ts +43 -0
  37. package/host-link/host-link.js +53 -0
  38. package/host-link/host-packages.d.ts +25 -0
  39. package/host-link/host-packages.js +9 -0
  40. package/host-link/host-workers.d.ts +75 -0
  41. package/host-link/host-workers.js +28 -0
  42. package/host-link/multiplexer.d.ts +115 -0
  43. package/host-link/multiplexer.js +288 -0
  44. package/host-link/sealed-box.d.ts +21 -0
  45. package/host-link/sealed-box.js +103 -0
  46. package/index.d.ts +16 -3
  47. package/index.js +16 -4
  48. package/interfaces/index.js +0 -1
  49. package/interfaces/logger.interface.js +0 -1
  50. package/models/enums/enum-source.enum.d.ts +11 -0
  51. package/models/enums/enum-source.enum.js +21 -0
  52. package/models/enums/log-channel-kind.d.ts +7 -0
  53. package/models/enums/log-channel-kind.js +13 -0
  54. package/models/enums/log-level.js +0 -1
  55. package/models/enums/service-status.d.ts +5 -1
  56. package/models/enums/service-status.js +4 -1
  57. package/models/enums/variable-format.enum.js +0 -1
  58. package/models/enums/variable-type.enum.js +0 -1
  59. package/models/enums/worker-permission.enum.d.ts +16 -0
  60. package/models/enums/worker-permission.enum.js +27 -0
  61. package/models/index.d.ts +6 -1
  62. package/models/index.js +6 -2
  63. package/models/metadata/app-metadata.d.ts +6 -0
  64. package/models/metadata/{processor-metadata.js → app-metadata.js} +6 -7
  65. package/models/metadata/component-metadata.js +0 -1
  66. package/models/metadata/element-component-metadata.d.ts +2 -0
  67. package/models/metadata/element-component-metadata.js +10 -3
  68. package/models/metadata/element-metadata.d.ts +8 -0
  69. package/models/metadata/element-metadata.js +40 -1
  70. package/models/metadata/log-channel-metadata.d.ts +23 -0
  71. package/models/metadata/log-channel-metadata.js +72 -0
  72. package/models/metadata/package-metadata.js +0 -1
  73. package/models/metadata/plugin-metadata.d.ts +15 -0
  74. package/models/metadata/plugin-metadata.js +65 -0
  75. package/models/metadata/variable-metadata.d.ts +3 -0
  76. package/models/metadata/variable-metadata.js +15 -2
  77. package/models/profile/log-options.d.ts +17 -0
  78. package/models/profile/log-options.js +58 -2
  79. package/models/profile/profile-component.js +0 -1
  80. package/models/profile/profile.d.ts +2 -2
  81. package/models/profile/profile.js +9 -10
  82. package/models-document.js +0 -1
  83. package/package.json +6 -2
  84. package/registry/docs-sidebar.d.ts +59 -0
  85. package/registry/docs-sidebar.js +169 -0
  86. package/registry/extension-package.d.ts +40 -13
  87. package/registry/extension-package.js +59 -19
  88. package/registry/extension-registry.d.ts +16 -16
  89. package/registry/extension-registry.js +59 -45
  90. package/registry/module-catalog.d.ts +31 -0
  91. package/registry/module-catalog.js +88 -0
  92. package/registry/package-docs.d.ts +98 -0
  93. package/registry/package-docs.js +159 -0
  94. package/registry/package-manifest.d.ts +54 -3
  95. package/registry/package-manifest.js +59 -2
  96. package/utils/decode-profile.d.ts +18 -2
  97. package/utils/decode-profile.js +65 -10
  98. package/utils/encrypt-helpers.d.ts +7 -0
  99. package/utils/encrypt-helpers.js +12 -6
  100. package/utils/locate-package-json.js +0 -1
  101. package/utils/materialize-metadata.d.ts +3 -3
  102. package/utils/materialize-metadata.js +16 -13
  103. package/utils/normalize-metadata.d.ts +23 -4
  104. package/utils/normalize-metadata.js +52 -7
  105. package/utils/registry-names.js +0 -1
  106. package/utils/resolve-promises.js +0 -1
  107. package/utils/type-guards.d.ts +2 -2
  108. package/utils/type-guards.js +4 -5
  109. package/utils/uri-to-id.js +0 -1
  110. package/utils/worker-permissions.d.ts +25 -0
  111. package/utils/worker-permissions.js +59 -0
  112. package/classes/base-element.js.map +0 -1
  113. package/classes/component-base.js.map +0 -1
  114. package/classes/console-logger.js.map +0 -1
  115. package/classes/frame-stream.js.map +0 -1
  116. package/classes/processor-base.js +0 -11
  117. package/classes/processor-base.js.map +0 -1
  118. package/classes/runnable.js.map +0 -1
  119. package/classes/sb-error.js.map +0 -1
  120. package/classes/stack-executor.js.map +0 -1
  121. package/constants.js.map +0 -1
  122. package/decorators/component.decorator.js.map +0 -1
  123. package/decorators/decorator-helpers.js.map +0 -1
  124. package/decorators/define-variable.decorator.js.map +0 -1
  125. package/decorators/processor-events.decorator.d.ts +0 -5
  126. package/decorators/processor-events.decorator.js.map +0 -1
  127. package/decorators/processor.decorator.d.ts +0 -29
  128. package/decorators/processor.decorator.js.map +0 -1
  129. package/decorators/use-component.decorator.js.map +0 -1
  130. package/decorators/use-variables.decorator.js.map +0 -1
  131. package/index.js.map +0 -1
  132. package/interfaces/index.js.map +0 -1
  133. package/interfaces/logger.interface.js.map +0 -1
  134. package/models/enums/log-level.js.map +0 -1
  135. package/models/enums/service-status.js.map +0 -1
  136. package/models/enums/variable-format.enum.js.map +0 -1
  137. package/models/enums/variable-type.enum.js.map +0 -1
  138. package/models/index.js.map +0 -1
  139. package/models/metadata/component-metadata.js.map +0 -1
  140. package/models/metadata/element-component-metadata.js.map +0 -1
  141. package/models/metadata/element-metadata.js.map +0 -1
  142. package/models/metadata/package-metadata.js.map +0 -1
  143. package/models/metadata/processor-metadata.d.ts +0 -6
  144. package/models/metadata/processor-metadata.js.map +0 -1
  145. package/models/metadata/variable-metadata.js.map +0 -1
  146. package/models/profile/log-options.js.map +0 -1
  147. package/models/profile/profile-component.js.map +0 -1
  148. package/models/profile/profile.js.map +0 -1
  149. package/models-document.js.map +0 -1
  150. package/processor-factory.d.ts +0 -26
  151. package/processor-factory.js.map +0 -1
  152. package/registry/extension-package.js.map +0 -1
  153. package/registry/extension-registry.js.map +0 -1
  154. package/registry/package-manifest.js.map +0 -1
  155. package/utils/decode-profile.js.map +0 -1
  156. package/utils/encrypt-helpers.js.map +0 -1
  157. package/utils/locate-package-json.js.map +0 -1
  158. package/utils/materialize-metadata.js.map +0 -1
  159. package/utils/normalize-metadata.js.map +0 -1
  160. package/utils/registry-names.js.map +0 -1
  161. package/utils/resolve-promises.js.map +0 -1
  162. package/utils/type-guards.js.map +0 -1
  163. package/utils/uri-to-id.js.map +0 -1
@@ -5,20 +5,21 @@ import { updateErrorMessage } from '@jsopen/objects';
5
5
  import { ResponsiveMap } from '@opra/common';
6
6
  import { AsyncEventEmitter } from 'node-events-async';
7
7
  import semver from 'semver';
8
+ import { AppBase } from '../classes/app-base.js';
8
9
  import { ComponentBase } from '../classes/component-base.js';
9
- import { ProcessorBase } from '../classes/processor-base.js';
10
10
  import { SbError } from '../classes/sb-error.js';
11
11
  import { CLASS_METADATA, CLASS_TYPE } from '../constants.js';
12
12
  import { locatePackageJson } from '../utils/locate-package-json.js';
13
- import { normalizeComponentMetadata, normalizeProcessorMetadata, } from '../utils/normalize-metadata.js';
13
+ import { normalizeAppMetadata, normalizeComponentMetadata, normalizePluginMetadata, } from '../utils/normalize-metadata.js';
14
14
  import { uriToId } from '../utils/uri-to-id.js';
15
- import { ComponentExtension, ExtensionPackage, ProcessorExtension, } from './extension-package.js';
15
+ import { AppExtension, ComponentExtension, ExtensionPackage, PluginExtension, } from './extension-package.js';
16
+ import { readExtensionManifest } from './package-manifest.js';
16
17
  const instanceSymbol = Symbol.for('instance');
17
18
  /**
18
- * The `ExtensionRegistry` class is responsible for managing component and processor extensions,
19
+ * The `ExtensionRegistry` class is responsible for managing component and app extensions,
19
20
  * as well as handling the registration of extension packages. It provides methods to locate and
20
21
  * retrieve extensions by their class name and version, and offers the ability to dynamically register
21
- * packages, components, and processors at runtime.
22
+ * packages, components, and apps at runtime.
22
23
  *
23
24
  * @class ExtensionRegistry
24
25
  */
@@ -29,7 +30,7 @@ export class ExtensionRegistry extends AsyncEventEmitter {
29
30
  /** Registries made current with `use`, the last one in effect */
30
31
  static _inUse = [];
31
32
  /**
32
- * The registry the static methods act on, and so ProcessorFactory and
33
+ * The registry the static methods act on, and so AppFactory and
33
34
  * the metadata helpers: the process's own, unless another is in use.
34
35
  */
35
36
  static get current() {
@@ -64,11 +65,11 @@ export class ExtensionRegistry extends AsyncEventEmitter {
64
65
  static get componentsByClassName() {
65
66
  return this[instanceSymbol].componentsByClassName;
66
67
  }
67
- static get processors() {
68
- return this[instanceSymbol].processors;
68
+ static get apps() {
69
+ return this[instanceSymbol].apps;
69
70
  }
70
- static get processorsByClassName() {
71
- return this[instanceSymbol].processorsByClassName;
71
+ static get appsByClassName() {
72
+ return this[instanceSymbol].appsByClassName;
72
73
  }
73
74
  static setResolver(fn) {
74
75
  this[instanceSymbol].setResolver(fn);
@@ -79,11 +80,11 @@ export class ExtensionRegistry extends AsyncEventEmitter {
79
80
  static getComponent(uri, version = '*') {
80
81
  return this[instanceSymbol].getComponent(uri, version);
81
82
  }
82
- static findProcessor(uri, version = '*') {
83
- return this[instanceSymbol].findProcessor(uri, version);
83
+ static findApp(uri, version = '*') {
84
+ return this[instanceSymbol].findApp(uri, version);
84
85
  }
85
- static getProcessor(uri, version = '*') {
86
- return this[instanceSymbol].getProcessor(uri, version);
86
+ static getApp(uri, version = '*') {
87
+ return this[instanceSymbol].getApp(uri, version);
87
88
  }
88
89
  static async registerPackage(specifier) {
89
90
  return this[instanceSymbol].registerPackage(specifier);
@@ -100,8 +101,8 @@ export class ExtensionRegistry extends AsyncEventEmitter {
100
101
  static onRegisterComponent(fn) {
101
102
  this[instanceSymbol].on('register-component', fn);
102
103
  }
103
- static onRegisterProcessor(fn) {
104
- this[instanceSymbol].on('register-processor', fn);
104
+ static onRegisterApp(fn) {
105
+ this[instanceSymbol].on('register-app', fn);
105
106
  }
106
107
  constructor() {
107
108
  super();
@@ -111,8 +112,8 @@ export class ExtensionRegistry extends AsyncEventEmitter {
111
112
  packages = new ResponsiveMap();
112
113
  components = new ResponsiveMap();
113
114
  componentsByClassName = new ResponsiveMap();
114
- processors = new ResponsiveMap();
115
- processorsByClassName = new ResponsiveMap();
115
+ apps = new ResponsiveMap();
116
+ appsByClassName = new ResponsiveMap();
116
117
  /**
117
118
  *
118
119
  */
@@ -140,18 +141,18 @@ export class ExtensionRegistry extends AsyncEventEmitter {
140
141
  /**
141
142
  *
142
143
  */
143
- findProcessor(uri, version = '*') {
144
- return this.processors
144
+ findApp(uri, version = '*') {
145
+ return this.apps
145
146
  .get(uriToId(uri))
146
147
  ?.find(v => semver.satisfies(v.version, version || '*'));
147
148
  }
148
149
  /**
149
150
  *
150
151
  */
151
- getProcessor(uri, version = '*') {
152
- const cmp = this.findProcessor(uri, version);
152
+ getApp(uri, version = '*') {
153
+ const cmp = this.findApp(uri, version);
153
154
  if (!cmp) {
154
- throw new SbError(`Processor "${uri}${version ? `:${version}` : ''}" not found in registry`);
155
+ throw new SbError(`App "${uri}${version ? `:${version}` : ''}" not found in registry`);
155
156
  }
156
157
  return cmp;
157
158
  }
@@ -168,8 +169,11 @@ export class ExtensionRegistry extends AsyncEventEmitter {
168
169
  if (path.isAbsolute(entryPoint) && process.platform === 'win32') {
169
170
  entryPoint = pathToFileURL(entryPoint).href;
170
171
  }
172
+ const read = readExtensionManifest(pkgDir, pkgJson);
171
173
  const pkg = new ExtensionPackage();
172
- pkg.assignManifest(pkgJson);
174
+ pkg.assignManifest(pkgJson, read?.manifest);
175
+ for (const field of read?.unknownFields || [])
176
+ pkg.warnings.push(`The manifest of "${pkg.name}": "${field}" is not known, passed over`);
173
177
  pkg.path = pkgDir;
174
178
  pkg.entryPoint = entryPoint;
175
179
  const module = await import(entryPoint);
@@ -194,19 +198,30 @@ export class ExtensionRegistry extends AsyncEventEmitter {
194
198
  pkg.components = pkg.components || [];
195
199
  pkg.components.push(new ComponentExtension(pkg, ctorName, metadata));
196
200
  }
197
- else if (classType === 'processor') {
201
+ else if (classType === 'app') {
198
202
  try {
199
- metadata = await normalizeProcessorMetadata(metadata);
203
+ metadata = await normalizeAppMetadata(metadata);
200
204
  }
201
205
  catch (e) {
202
- updateErrorMessage(e, `Processor (${ctorName}) metadata validation error. ` + e.message);
206
+ updateErrorMessage(e, `App (${ctorName}) metadata validation error. ` + e.message);
203
207
  pkg.errors.push(e);
204
208
  continue;
205
209
  }
206
210
  if (metadata.abstract)
207
211
  continue;
208
- pkg.processors = pkg.processors || [];
209
- pkg.processors.push(new ProcessorExtension(pkg, ctorName, metadata));
212
+ pkg.apps = pkg.apps || [];
213
+ pkg.apps.push(new AppExtension(pkg, ctorName, metadata));
214
+ }
215
+ else if (classType === 'plugin') {
216
+ try {
217
+ metadata = await normalizePluginMetadata(metadata);
218
+ }
219
+ catch (e) {
220
+ updateErrorMessage(e, `Plugin (${ctorName}) metadata validation error. ` + e.message);
221
+ pkg.errors.push(e);
222
+ continue;
223
+ }
224
+ pkg.plugins.push(new PluginExtension(pkg, ctorName, metadata));
210
225
  }
211
226
  }
212
227
  this.storePackage(pkg);
@@ -222,8 +237,8 @@ export class ExtensionRegistry extends AsyncEventEmitter {
222
237
  if (classType === 'component') {
223
238
  metadata = await normalizeComponentMetadata(metadata);
224
239
  }
225
- else if (classType === 'processor') {
226
- metadata = await normalizeProcessorMetadata(metadata);
240
+ else if (classType === 'app') {
241
+ metadata = await normalizeAppMetadata(metadata);
227
242
  }
228
243
  else
229
244
  /* c8 ignore next */
@@ -250,8 +265,8 @@ export class ExtensionRegistry extends AsyncEventEmitter {
250
265
  pkg.components.push(out);
251
266
  }
252
267
  else {
253
- out = new ProcessorExtension(pkg, ctor.name, metadata, ctor);
254
- pkg.processors.push(out);
268
+ out = new AppExtension(pkg, ctor.name, metadata, ctor);
269
+ pkg.apps.push(out);
255
270
  }
256
271
  this.storePackage(pkg);
257
272
  return out;
@@ -273,8 +288,8 @@ export class ExtensionRegistry extends AsyncEventEmitter {
273
288
  pkgVersions.sort((a, b) => semver.compare(b.version, a.version));
274
289
  if (pkg.components?.length) {
275
290
  for (const cmp of pkg.components) {
276
- if (this.findProcessor(cmp.uri, '*'))
277
- throw new SbError(`Component "${cmp.uri}" is already registered as a processor.`);
291
+ if (this.findApp(cmp.uri, '*'))
292
+ throw new SbError(`Component "${cmp.uri}" is already registered as an app.`);
278
293
  /** Store to components */
279
294
  const id = uriToId(cmp.uri);
280
295
  let items = this.components.get(id);
@@ -301,16 +316,16 @@ export class ExtensionRegistry extends AsyncEventEmitter {
301
316
  this.emitSafe('register-component', cmp);
302
317
  }
303
318
  }
304
- if (pkg.processors?.length) {
305
- for (const prc of pkg.processors) {
319
+ if (pkg.apps?.length) {
320
+ for (const prc of pkg.apps) {
306
321
  if (this.findComponent(prc.uri, '*'))
307
- throw new SbError(`Processor "${prc.uri}" is already registered as a component.`);
308
- /** Store to processors */
322
+ throw new SbError(`App "${prc.uri}" is already registered as a component.`);
323
+ /** Store to apps */
309
324
  const id = uriToId(prc.uri);
310
- let items = this.processors.get(id);
325
+ let items = this.apps.get(id);
311
326
  if (!items) {
312
327
  items = [];
313
- this.processors.set(id, items);
328
+ this.apps.set(id, items);
314
329
  }
315
330
  i = items.findIndex(v => v.version === prc.version);
316
331
  if (i >= 0)
@@ -320,10 +335,10 @@ export class ExtensionRegistry extends AsyncEventEmitter {
320
335
  items.sort((a, b) => semver.compare(b.version, a.version));
321
336
  }
322
337
  /** Store to componentsByClassName */
323
- items = this.processors.get(prc.className);
338
+ items = this.apps.get(prc.className);
324
339
  if (!items) {
325
340
  items = [];
326
- this.processorsByClassName.set(prc.className, items);
341
+ this.appsByClassName.set(prc.className, items);
327
342
  }
328
343
  i = items.findIndex(v => v.version === prc.version);
329
344
  if (i >= 0)
@@ -332,10 +347,9 @@ export class ExtensionRegistry extends AsyncEventEmitter {
332
347
  items.push(prc);
333
348
  items.sort((a, b) => semver.compare(b.version, a.version));
334
349
  }
335
- this.emitSafe('register-processor', prc);
350
+ this.emitSafe('register-app', prc);
336
351
  }
337
352
  }
338
353
  this.emitSafe('register-package', pkg);
339
354
  }
340
355
  }
341
- //# sourceMappingURL=extension-registry.js.map
@@ -0,0 +1,31 @@
1
+ export declare const SUMMARY_MAX_LENGTH = 160;
2
+ export declare const SCREENSHOTS_MAX = 8;
3
+ /** A summary as told: one in the package's docs language, or one per language */
4
+ export type ModuleSummary = string | Record<string, string>;
5
+ /** What the Marketplace is told of a module, as its makers wrote it */
6
+ export interface ModuleCatalogInput {
7
+ className: string;
8
+ summary?: ModuleSummary;
9
+ screenshots?: string[];
10
+ }
11
+ /** What is kept of it: its summary by language, its screenshots' paths */
12
+ export interface ModuleCatalog {
13
+ summary?: Record<string, string>;
14
+ screenshots?: string[];
15
+ }
16
+ /**
17
+ * A module's Marketplace fields checked, and made what is kept: its
18
+ * summary by language (one alone, in the package's docs language), its
19
+ * screenshots as paths in the package (docs/<ClassName>/...), each among
20
+ * its documents. Problems told, none thrown: spm and the registry tell
21
+ * them their own way.
22
+ */
23
+ export declare function checkModuleCatalog(input: ModuleCatalogInput, context: {
24
+ /** The package's docs language */
25
+ docsLanguage: string;
26
+ /** A file is among the package's documents */
27
+ hasDoc: (file: string) => boolean;
28
+ }): {
29
+ catalog: ModuleCatalog;
30
+ problems: string[];
31
+ };
@@ -0,0 +1,88 @@
1
+ import { DOC_TYPES, DOCS_DIR, isDocsLanguage } from './package-docs.js';
2
+ /*
3
+ * How an app or a component is told in the Marketplace: what it is for, in
4
+ * a sentence (its summary), and how it looks (its screenshots, among its
5
+ * documents - not installed). Told by its makers on @App / @Component,
6
+ * checked by spm as it packs and by the registry as it takes the package.
7
+ * Where it is shown - what it does, what it talks to - is not its own to
8
+ * tell: the registry's groups, asked for in Console and decided there.
9
+ */
10
+ export const SUMMARY_MAX_LENGTH = 160;
11
+ export const SCREENSHOTS_MAX = 8;
12
+ /**
13
+ * A module's Marketplace fields checked, and made what is kept: its
14
+ * summary by language (one alone, in the package's docs language), its
15
+ * screenshots as paths in the package (docs/<ClassName>/...), each among
16
+ * its documents. Problems told, none thrown: spm and the registry tell
17
+ * them their own way.
18
+ */
19
+ export function checkModuleCatalog(input, context) {
20
+ const problems = [];
21
+ const catalog = {};
22
+ const name = input.className;
23
+ if (input.summary !== undefined) {
24
+ const told = typeof input.summary === 'string'
25
+ ? { [context.docsLanguage]: input.summary }
26
+ : input.summary && typeof input.summary === 'object'
27
+ ? input.summary
28
+ : {};
29
+ if (typeof input.summary !== 'string' && !Object.keys(told).length)
30
+ problems.push(`${name}'s summary is a sentence, or one per language: { en: '...', tr: '...' }`);
31
+ const summary = {};
32
+ for (const [language, text] of Object.entries(told)) {
33
+ if (!isDocsLanguage(language)) {
34
+ problems.push(`${name}'s summary is told in "${language}", not a language: en, tr, pt-BR...`);
35
+ continue;
36
+ }
37
+ if (typeof text !== 'string' || !text.trim()) {
38
+ problems.push(`${name}'s summary in ${language} is empty`);
39
+ continue;
40
+ }
41
+ const sentence = text.trim().replace(/\s+/g, ' ');
42
+ if (sentence.length > SUMMARY_MAX_LENGTH) {
43
+ problems.push(`${name}'s summary in ${language} is ${sentence.length} characters: ${SUMMARY_MAX_LENGTH} at most, a sentence`);
44
+ continue;
45
+ }
46
+ summary[language] = sentence;
47
+ }
48
+ if (Object.keys(summary).length)
49
+ catalog.summary = summary;
50
+ }
51
+ if (input.screenshots !== undefined) {
52
+ const own = `${DOCS_DIR}/${name}/`;
53
+ const screenshots = [];
54
+ for (const shot of input.screenshots) {
55
+ /* Told within its own documents' folder, or as a path in the package */
56
+ const file = typeof shot === 'string'
57
+ ? shot.startsWith(own)
58
+ ? shot
59
+ : own + shot.replace(/^\.?\//, '')
60
+ : '';
61
+ if (!file.startsWith(own) || file.includes('..')) {
62
+ problems.push(`${name}'s screenshot ${String(shot)} is not in ${own}: its own documents`);
63
+ continue;
64
+ }
65
+ const type = DOC_TYPES[extensionOf(file)];
66
+ if (type?.kind !== 'image') {
67
+ problems.push(`${name}'s screenshot ${file} is not an image: PNG, JPEG, GIF, WebP or SVG`);
68
+ continue;
69
+ }
70
+ if (!context.hasDoc(file)) {
71
+ problems.push(`${name}'s screenshot ${file} is not among the package's documents`);
72
+ continue;
73
+ }
74
+ screenshots.push(file);
75
+ }
76
+ if (screenshots.length > SCREENSHOTS_MAX)
77
+ problems.push(`${name} tells ${screenshots.length} screenshots: ${SCREENSHOTS_MAX} at most`);
78
+ else if (screenshots.length)
79
+ catalog.screenshots = [...new Set(screenshots)];
80
+ }
81
+ return { catalog, problems };
82
+ }
83
+ /** A file's extension, lower-cased: ".png" */
84
+ function extensionOf(file) {
85
+ const base = file.slice(file.lastIndexOf('/') + 1);
86
+ const dot = base.lastIndexOf('.');
87
+ return dot > 0 ? base.slice(dot).toLowerCase() : '';
88
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * A package's documents: docs/ in its source, put in what spm packs, taken
3
+ * out of it by the registry on publish - the field reads them from the
4
+ * registry, never from what it installs.
5
+ *
6
+ * docs/<ClassName>/README.md an app's, component's or plugin's page
7
+ * docs/<ClassName>/** its documents
8
+ * docs/** the package's own (guides/, images/)
9
+ *
10
+ * A document is Markdown or PDF; an image is what they show. A document's
11
+ * languages share its name, each other than the package's own before its
12
+ * extension: network.md, network.tr.md, network.pt-BR.md - the one naming
13
+ * none in the language the manifest's `docsLanguage` names (English, if
14
+ * none). A document may be in another language alone (user-guide.en.pdf).
15
+ * How they are listed, a sidebar.json may tell (see docs-sidebar).
16
+ */
17
+ export declare const DOCS_DIR = "docs";
18
+ /** An app's, component's or plugin's page, in docs/<ClassName>/ */
19
+ export declare const README_FILE = "README.md";
20
+ /** The language of documents that name none, when the manifest names none */
21
+ export declare const DEFAULT_DOCS_LANGUAGE = "en";
22
+ /** All of a package's documents together, at most */
23
+ export declare const DOCS_MAX_TOTAL_BYTES: number;
24
+ /** What a file in docs/ is: read as a page, or shown in one */
25
+ export type DocKind = 'document' | 'image';
26
+ export interface DocType {
27
+ kind: DocKind;
28
+ contentType: string;
29
+ /** Its largest: larger, it is refused */
30
+ maxBytes: number;
31
+ }
32
+ /** The files docs/ may hold, by extension: no other is packed */
33
+ export declare const DOC_TYPES: Readonly<Record<string, DocType>>;
34
+ /**
35
+ * A directory in docs/ named as a class would be (UdpSocket): an app's,
36
+ * component's or plugin's. Others (guides, images, API) the package's own.
37
+ */
38
+ export declare const CLASS_DIR_PATTERN: RegExp;
39
+ /** Whether a document may be in this language: `tr`, `pt-BR` */
40
+ export declare function isDocsLanguage(code: unknown): code is string;
41
+ /** A file in docs/, as its path tells */
42
+ export interface DocPath {
43
+ /** Its path in the package: docs/ModbusGateway/network.tr.md */
44
+ path: string;
45
+ type: DocType;
46
+ /** The class whose documents it is among; none: the package's own */
47
+ module?: string;
48
+ /** Its language, when its name tells one: a document's alone */
49
+ language?: string;
50
+ /**
51
+ * The document it is in another language of: docs/ModbusGateway/network.md
52
+ * for network.tr.md - itself, when it names no language
53
+ */
54
+ base: string;
55
+ }
56
+ /**
57
+ * Reads what a file in docs/ is from its path in the package (docs/...).
58
+ * Throws when docs/ may not hold it: a type it does not take, a name a link
59
+ * would need escaping for, or no path in docs/.
60
+ */
61
+ export declare function parseDocPath(file: string): DocPath;
62
+ /**
63
+ * Whether a file's bytes are what its extension says: a PDF begins as one,
64
+ * an image as its type, Markdown is UTF-8 text
65
+ */
66
+ export declare function isDocContent(type: DocType, content: Uint8Array): boolean;
67
+ /**
68
+ * What a publish tells of a package's documents when it leaves out the
69
+ * files its registry has: at the archive's root, each file of docs/ by its
70
+ * path, SHA-256 and size. A Markdown document is always sent - what it
71
+ * tells is read from it; a PDF or an image may be left out, if the
72
+ * package's earlier versions had it.
73
+ */
74
+ export declare const DOCS_MANIFEST_FILE = "syncbridge-docs.json";
75
+ export interface DocsManifest {
76
+ files: DocsManifestFile[];
77
+ }
78
+ export interface DocsManifestFile {
79
+ /** Its path in the package: docs/Gw/user-guide.en.pdf */
80
+ path: string;
81
+ /** Hex SHA-256 of the file */
82
+ sha256: string;
83
+ size: number;
84
+ }
85
+ /** Whether a publish may leave a file out, its registry having it */
86
+ export declare function isDocLeftOut(type: DocType): boolean;
87
+ /** A file of a package, by its path in the archive and its SHA-256 */
88
+ export interface PackageFileDigest {
89
+ path: string;
90
+ sha256: string;
91
+ }
92
+ /**
93
+ * What a package holds, as one SHA-256: every file of its archive by its
94
+ * path and hash, sorted - the same however the archive was zipped, or
95
+ * whichever of its documents' files were left out (told by their hash).
96
+ * A publish is approved for it.
97
+ */
98
+ export declare function packageDigest(files: readonly PackageFileDigest[]): Promise<string>;
@@ -0,0 +1,159 @@
1
+ import { SbError } from '../classes/sb-error.js';
2
+ /**
3
+ * A package's documents: docs/ in its source, put in what spm packs, taken
4
+ * out of it by the registry on publish - the field reads them from the
5
+ * registry, never from what it installs.
6
+ *
7
+ * docs/<ClassName>/README.md an app's, component's or plugin's page
8
+ * docs/<ClassName>/** its documents
9
+ * docs/** the package's own (guides/, images/)
10
+ *
11
+ * A document is Markdown or PDF; an image is what they show. A document's
12
+ * languages share its name, each other than the package's own before its
13
+ * extension: network.md, network.tr.md, network.pt-BR.md - the one naming
14
+ * none in the language the manifest's `docsLanguage` names (English, if
15
+ * none). A document may be in another language alone (user-guide.en.pdf).
16
+ * How they are listed, a sidebar.json may tell (see docs-sidebar).
17
+ */
18
+ export const DOCS_DIR = 'docs';
19
+ /** An app's, component's or plugin's page, in docs/<ClassName>/ */
20
+ export const README_FILE = 'README.md';
21
+ /** The language of documents that name none, when the manifest names none */
22
+ export const DEFAULT_DOCS_LANGUAGE = 'en';
23
+ /** All of a package's documents together, at most */
24
+ export const DOCS_MAX_TOTAL_BYTES = 50 * 1024 * 1024;
25
+ const KB = 1024;
26
+ const MB = 1024 * KB;
27
+ /** The files docs/ may hold, by extension: no other is packed */
28
+ export const DOC_TYPES = {
29
+ '.md': {
30
+ kind: 'document',
31
+ contentType: 'text/markdown; charset=utf-8',
32
+ maxBytes: 256 * KB,
33
+ },
34
+ '.pdf': {
35
+ kind: 'document',
36
+ contentType: 'application/pdf',
37
+ maxBytes: 20 * MB,
38
+ },
39
+ '.png': { kind: 'image', contentType: 'image/png', maxBytes: 5 * MB },
40
+ '.jpg': { kind: 'image', contentType: 'image/jpeg', maxBytes: 5 * MB },
41
+ '.jpeg': { kind: 'image', contentType: 'image/jpeg', maxBytes: 5 * MB },
42
+ '.gif': { kind: 'image', contentType: 'image/gif', maxBytes: 5 * MB },
43
+ '.webp': { kind: 'image', contentType: 'image/webp', maxBytes: 5 * MB },
44
+ '.svg': { kind: 'image', contentType: 'image/svg+xml', maxBytes: 5 * MB },
45
+ };
46
+ /**
47
+ * A directory in docs/ named as a class would be (UdpSocket): an app's,
48
+ * component's or plugin's. Others (guides, images, API) the package's own.
49
+ */
50
+ export const CLASS_DIR_PATTERN = /^[A-Z][a-z0-9][A-Za-z0-9]*$/;
51
+ /** A file's or directory's name in docs/: one a link needs no escaping for */
52
+ const NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
53
+ /** A language a document is in: ISO 639-1, its region if any (pt-BR) */
54
+ const LANGUAGE_PATTERN = /^([a-z]{2})(?:-([A-Z]{2}))?$/;
55
+ /** A document's language in its name: network.tr.md, setup.pt-BR.pdf */
56
+ const LANGUAGE_IN_NAME = /^(.+)\.([a-z]{2}(?:-[A-Z]{2})?)(\.(?:md|pdf))$/;
57
+ /** ISO 639-1: the languages a document may be in */
58
+ const LANGUAGES = new Set(('aa ab ae af ak am an ar as av ay az ba be bg bh bi bm bn bo br bs ca ce ' +
59
+ 'ch co cr cs cu cv cy da de dv dz ee el en eo es et eu fa ff fi fj fo fr ' +
60
+ 'fy ga gd gl gn gu gv ha he hi ho hr ht hu hy hz ia id ie ig ii ik io is ' +
61
+ 'it iu ja jv ka kg ki kj kk kl km kn ko kr ks ku kv kw ky la lb lg li ln ' +
62
+ 'lo lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ' +
63
+ 'ny oc oj om or os pa pi pl ps pt qu rm rn ro ru rw sa sc sd se sg si sk ' +
64
+ 'sl sm sn so sq sr ss st su sv sw ta te tg th ti tk tl tn to tr ts tt tw ' +
65
+ 'ty ug uk ur uz ve vi vo wa wo xh yi yo za zh zu').split(' '));
66
+ /** Whether a document may be in this language: `tr`, `pt-BR` */
67
+ export function isDocsLanguage(code) {
68
+ if (typeof code !== 'string')
69
+ return false;
70
+ const m = LANGUAGE_PATTERN.exec(code);
71
+ return !!m && LANGUAGES.has(m[1]);
72
+ }
73
+ /**
74
+ * Reads what a file in docs/ is from its path in the package (docs/...).
75
+ * Throws when docs/ may not hold it: a type it does not take, a name a link
76
+ * would need escaping for, or no path in docs/.
77
+ */
78
+ export function parseDocPath(file) {
79
+ const segments = file.split('/');
80
+ if (segments[0] !== DOCS_DIR || segments.length < 2)
81
+ throw new SbError(`${file}: not in ${DOCS_DIR}/`);
82
+ const names = segments.slice(1);
83
+ const bad = names.find(n => !NAME_PATTERN.test(n));
84
+ if (bad !== undefined)
85
+ throw new SbError(`${file}: "${bad}" is not a name docs/ takes - letters, digits, ".", "_" and "-" alone, not starting with "." "_" or "-"`);
86
+ const name = names[names.length - 1];
87
+ const dot = name.lastIndexOf('.');
88
+ const type = dot > 0 ? DOC_TYPES[name.slice(dot).toLowerCase()] : undefined;
89
+ if (!type)
90
+ throw new SbError(`${file}: docs/ takes ${Object.keys(DOC_TYPES).join(', ')} files alone`);
91
+ const module = names.length > 1 && CLASS_DIR_PATTERN.test(names[0]) ? names[0] : undefined;
92
+ const m = type.kind === 'document' ? LANGUAGE_IN_NAME.exec(name) : null;
93
+ if (m && LANGUAGES.has(m[2].slice(0, 2)))
94
+ return {
95
+ path: file,
96
+ type,
97
+ module,
98
+ language: m[2],
99
+ base: [...segments.slice(0, -1), m[1] + m[3]].join('/'),
100
+ };
101
+ return { path: file, type, module, base: file };
102
+ }
103
+ /** The first bytes each type begins with: what a file claims, checked */
104
+ const SIGNATURES = {
105
+ 'application/pdf': b => ascii(b, 0, 5) === '%PDF-',
106
+ 'image/png': b => ascii(b, 1, 4) === 'PNG' && b[0] === 0x89,
107
+ 'image/jpeg': b => b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff,
108
+ 'image/gif': b => ascii(b, 0, 4) === 'GIF8',
109
+ 'image/webp': b => ascii(b, 0, 4) === 'RIFF' && ascii(b, 8, 12) === 'WEBP',
110
+ 'image/svg+xml': b => /<svg[\s>]/.test(ascii(b, 0, Math.min(b.length, 4096))),
111
+ };
112
+ function ascii(b, start, end) {
113
+ return String.fromCharCode(...b.subarray(start, end));
114
+ }
115
+ /**
116
+ * Whether a file's bytes are what its extension says: a PDF begins as one,
117
+ * an image as its type, Markdown is UTF-8 text
118
+ */
119
+ export function isDocContent(type, content) {
120
+ if (type.contentType.startsWith('text/markdown')) {
121
+ try {
122
+ new TextDecoder('utf-8', { fatal: true }).decode(content);
123
+ return true;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ }
129
+ return SIGNATURES[type.contentType]?.(content) ?? false;
130
+ }
131
+ /**
132
+ * What a publish tells of a package's documents when it leaves out the
133
+ * files its registry has: at the archive's root, each file of docs/ by its
134
+ * path, SHA-256 and size. A Markdown document is always sent - what it
135
+ * tells is read from it; a PDF or an image may be left out, if the
136
+ * package's earlier versions had it.
137
+ */
138
+ export const DOCS_MANIFEST_FILE = 'syncbridge-docs.json';
139
+ /** Whether a publish may leave a file out, its registry having it */
140
+ export function isDocLeftOut(type) {
141
+ return !type.contentType.startsWith('text/markdown');
142
+ }
143
+ /**
144
+ * What a package holds, as one SHA-256: every file of its archive by its
145
+ * path and hash, sorted - the same however the archive was zipped, or
146
+ * whichever of its documents' files were left out (told by their hash).
147
+ * A publish is approved for it.
148
+ */
149
+ export async function packageDigest(files) {
150
+ const lines = files
151
+ .filter(f => f.path !== DOCS_MANIFEST_FILE && !f.path.endsWith('/'))
152
+ .map(f => `${f.path}\t${f.sha256}\n`)
153
+ .sort()
154
+ .join('');
155
+ const hash = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(lines));
156
+ return [...new Uint8Array(hash)]
157
+ .map(b => b.toString(16).padStart(2, '0'))
158
+ .join('');
159
+ }