@ham2k/extension-tools 1.15.0 → 1.16.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/h2kext-build.mjs +38 -16
- package/h2kext.mjs +14 -5
- package/package.json +1 -1
package/h2kext-build.mjs
CHANGED
|
@@ -25,7 +25,7 @@ import { createRequire } from "node:module"
|
|
|
25
25
|
import { dirname, join, resolve } from "node:path"
|
|
26
26
|
import vm from "node:vm"
|
|
27
27
|
|
|
28
|
-
import { H2KEXT_API_VERSION, MANIFEST_ENTRY, TIMERS_API_VERSION, sharedModulesFor } from "./h2kext.mjs"
|
|
28
|
+
import { H2KEXT_API_VERSION, HALO_GLOBAL_API_VERSION, MANIFEST_ENTRY, TIMERS_API_VERSION, sharedModulesFor } from "./h2kext.mjs"
|
|
29
29
|
|
|
30
30
|
/// What an extension's own `build.mjs` says, for everything that has to write
|
|
31
31
|
/// one: `h2kext-init`'s scaffold, and the samples the SDK ships.
|
|
@@ -160,7 +160,8 @@ export function sharedModulesPlugin({ sharedDependencies = {}, api = H2KEXT_API_
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
build.onLoad({ filter: /.*/, namespace: "ham2k-shared-module" }, (args) => ({
|
|
163
|
-
|
|
163
|
+
// `__polo` is the kernel's name on an app before extension API 16.
|
|
164
|
+
contents: `module.exports = (globalThis.__halo ?? globalThis.__polo).sharedModules[${JSON.stringify(args.path)}];`,
|
|
164
165
|
loader: "js",
|
|
165
166
|
}))
|
|
166
167
|
},
|
|
@@ -346,6 +347,7 @@ export async function buildExtension(esbuild, { dir, entry = "src/index.ts", out
|
|
|
346
347
|
}),
|
|
347
348
|
)
|
|
348
349
|
|
|
350
|
+
await checkKernelName({ bundle: join(out, "index.js"), manifest })
|
|
349
351
|
await checkSettingsPanelDeclared({ bundle: join(out, "index.js"), manifest, dir })
|
|
350
352
|
|
|
351
353
|
// The packer reads both from one directory, so the manifest the bundle was
|
|
@@ -376,6 +378,22 @@ export async function buildExtension(esbuild, { dir, entry = "src/index.ts", out
|
|
|
376
378
|
/// told the check did not run.
|
|
377
379
|
const ACTIVATION_DEADLINE_MS = 2000
|
|
378
380
|
|
|
381
|
+
/// Refuses a bundle that declares extension API 16 or more but was built with
|
|
382
|
+
/// an SDK that reaches the kernel only as \`__polo\`, which such an app answers
|
|
383
|
+
/// for nothing that declares that level. The SDK and the tools are separate
|
|
384
|
+
/// packages, so a project can update one and not the other.
|
|
385
|
+
async function checkKernelName({ bundle, manifest }) {
|
|
386
|
+
const api = manifest.api ?? H2KEXT_API_VERSION
|
|
387
|
+
if (api < HALO_GLOBAL_API_VERSION) return
|
|
388
|
+
const source = await readFile(bundle, "utf8")
|
|
389
|
+
if (source.includes("__halo") || !source.includes("__polo")) return
|
|
390
|
+
throw new BuildError(
|
|
391
|
+
`${MANIFEST_ENTRY} declares extension API ${api}, where the app's kernel is \`__halo\`, but this bundle's ` +
|
|
392
|
+
`@ham2k/extension-sdk reaches it only as \`__polo\`, which the app keeps for bundles declaring a level below ` +
|
|
393
|
+
`${HALO_GLOBAL_API_VERSION}. Update @ham2k/extension-sdk to 1.${HALO_GLOBAL_API_VERSION} or later, or declare a lower "api".`,
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
|
|
379
397
|
async function checkSettingsPanelDeclared({ bundle, manifest, dir }) {
|
|
380
398
|
const require = createRequire(resolve(dir, "noop.js"))
|
|
381
399
|
const sharedModules = new Proxy(
|
|
@@ -395,23 +413,27 @@ async function checkSettingsPanelDeclared({ bundle, manifest, dir }) {
|
|
|
395
413
|
)
|
|
396
414
|
const hostCall = async (method) => (method === "getSettings" ? {} : method.startsWith("dbLookup") ? [] : null)
|
|
397
415
|
const definitions = []
|
|
416
|
+
const kernel = {
|
|
417
|
+
defineExtension: (definition) => definitions.push(definition),
|
|
418
|
+
hostCall,
|
|
419
|
+
log: () => {},
|
|
420
|
+
invokeLocal: async () => [],
|
|
421
|
+
invokeLocalSequential: async () => [],
|
|
422
|
+
updateInterpretation: () => {},
|
|
423
|
+
getAccountKvKey: (extensionKey) => extensionKey,
|
|
424
|
+
registerDynamicForm: () => {},
|
|
425
|
+
unregisterDynamicForm: () => {},
|
|
426
|
+
host: { showForm: async () => null },
|
|
427
|
+
sharedModules,
|
|
428
|
+
sharedVersions: {},
|
|
429
|
+
}
|
|
430
|
+
// The names the app answers for the level the manifest declares: `__polo`
|
|
431
|
+
// only below HALO_GLOBAL_API_VERSION, whose SDKs may predate `__halo`.
|
|
398
432
|
const sandbox = {
|
|
399
433
|
console: { log() {}, info() {}, warn() {}, error() {}, debug() {} },
|
|
400
434
|
sendMessage: () => {},
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
hostCall,
|
|
404
|
-
log: () => {},
|
|
405
|
-
invokeLocal: async () => [],
|
|
406
|
-
invokeLocalSequential: async () => [],
|
|
407
|
-
updateInterpretation: () => {},
|
|
408
|
-
getAccountKvKey: (extensionKey) => extensionKey,
|
|
409
|
-
registerDynamicForm: () => {},
|
|
410
|
-
unregisterDynamicForm: () => {},
|
|
411
|
-
host: { showForm: async () => null },
|
|
412
|
-
sharedModules,
|
|
413
|
-
sharedVersions: {},
|
|
414
|
-
},
|
|
435
|
+
__halo: kernel,
|
|
436
|
+
...((manifest.api ?? H2KEXT_API_VERSION) < HALO_GLOBAL_API_VERSION ? { __polo: kernel } : {}),
|
|
415
437
|
}
|
|
416
438
|
sandbox.globalThis = sandbox
|
|
417
439
|
|
package/h2kext.mjs
CHANGED
|
@@ -56,8 +56,9 @@ import yazl from "yazl"
|
|
|
56
56
|
/// `radio.status` — `when` on layers and controls, and the `radio` panel
|
|
57
57
|
/// trigger; 14 adds `host.fyi`, messages on the FYI line under the call
|
|
58
58
|
/// info; 15 adds `incomplete` on a live scoring verdict, the fields a
|
|
59
|
-
/// contact still needs before Enter logs it
|
|
60
|
-
|
|
59
|
+
/// contact still needs before Enter logs it; 16 names the kernel `__halo`,
|
|
60
|
+
/// and keeps `__polo` only for a bundle that declares a lower level.
|
|
61
|
+
export const H2KEXT_API_VERSION = 16
|
|
61
62
|
|
|
62
63
|
/// The first API with `host.webSocket`. A manifest declaring `webSockets`
|
|
63
64
|
/// under an older one is refused: an older app would install it and fail only
|
|
@@ -80,6 +81,12 @@ export const CLUSTER_API_VERSION = 10
|
|
|
80
81
|
/// install it and refuse every raw command it sends.
|
|
81
82
|
export const RADIO_RAW_API_VERSION = 11
|
|
82
83
|
|
|
84
|
+
/// The first API whose app names the kernel `__halo`. Below it, the app also
|
|
85
|
+
/// answers `__polo`; from it, nothing answers that name for the bundle. A
|
|
86
|
+
/// bundle declaring this level or more whose SDK reaches only `__polo` is
|
|
87
|
+
/// refused at build: it would fail to load on the app it declares.
|
|
88
|
+
export const HALO_GLOBAL_API_VERSION = 16
|
|
89
|
+
|
|
83
90
|
/// The first API with timers. The build points a bundle's bare `setTimeout`
|
|
84
91
|
/// and friends at the SDK's only from here on: an older app's kernel hands an
|
|
85
92
|
/// extension no timers, and a bundle declaring less says it runs there.
|
|
@@ -148,7 +155,7 @@ export const ASSETS_PREFIX = "assets/"
|
|
|
148
155
|
|
|
149
156
|
/// The libraries the extension host carries, per API version, so a bundle does
|
|
150
157
|
/// not have to ship its own copy of each. The kernel holds one instance and
|
|
151
|
-
/// hands it to every extension through `
|
|
158
|
+
/// hands it to every extension through `__halo.sharedModules`.
|
|
152
159
|
///
|
|
153
160
|
/// NAMES only. The VERSION a given app carries is that build's business, which
|
|
154
161
|
/// is the whole reason a bundle declares a RANGE in `sharedDependencies` and
|
|
@@ -189,6 +196,7 @@ SHARED_MODULES_BY_API[12] = SHARED_MODULES_BY_API[11]
|
|
|
189
196
|
SHARED_MODULES_BY_API[13] = SHARED_MODULES_BY_API[12]
|
|
190
197
|
SHARED_MODULES_BY_API[14] = SHARED_MODULES_BY_API[13]
|
|
191
198
|
SHARED_MODULES_BY_API[15] = SHARED_MODULES_BY_API[14]
|
|
199
|
+
SHARED_MODULES_BY_API[16] = SHARED_MODULES_BY_API[15]
|
|
192
200
|
|
|
193
201
|
/// What a scaffolded extension declares on day one: the eight modules the SDK
|
|
194
202
|
/// barrel itself reaches, so a project that has written no code yet still
|
|
@@ -511,7 +519,8 @@ export function validateManifest(manifest, { source = MANIFEST_ENTRY, forceName
|
|
|
511
519
|
}
|
|
512
520
|
|
|
513
521
|
/// Every library a bundle takes from the host rather than carrying its own
|
|
514
|
-
/// copy — the `
|
|
522
|
+
/// copy — the `sharedModules[...]` lookups on the kernel its build emitted,
|
|
523
|
+
/// under either of the kernel's names.
|
|
515
524
|
///
|
|
516
525
|
/// Taking them is the supported path and worth roughly thirty times the bundle
|
|
517
526
|
/// size. What the caller checks is that each one was DECLARED: the host carries
|
|
@@ -522,7 +531,7 @@ export function validateManifest(manifest, { source = MANIFEST_ENTRY, forceName
|
|
|
522
531
|
/// else's app.
|
|
523
532
|
export function findSharedModuleReferences(source) {
|
|
524
533
|
const hits = []
|
|
525
|
-
const pattern = /
|
|
534
|
+
const pattern = /__(?:halo|polo)\s*\)?\s*\.\s*sharedModules\s*\[\s*["']([^"']+)["']\s*\]/g
|
|
526
535
|
for (const match of source.matchAll(pattern)) hits.push(match[1])
|
|
527
536
|
return [...new Set(hits)]
|
|
528
537
|
}
|