@snap-rail/plugin-kit 0.2.1 → 0.2.2
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/lib/build.js +9 -3
- package/lib/manifest.js +21 -2
- package/lib/pack.js +28 -12
- package/lib/seeds.js +1 -0
- package/lib/types/build.d.ts +22 -1
- package/lib/types/manifest.d.ts +18 -0
- package/lib/types/pack.d.ts +1 -0
- package/package.json +2 -1
package/lib/build.js
CHANGED
|
@@ -173,7 +173,13 @@ function hostBundle(entry) {
|
|
|
173
173
|
* subpath to its module namespace, and the shell seeds
|
|
174
174
|
* `<package>/<subpath>` ids from that map after requiring the face.
|
|
175
175
|
*
|
|
176
|
-
*
|
|
176
|
+
* A package that renders in more than one window calls this once per surface:
|
|
177
|
+
* the main surface keeps the package name as its module id and `client` as its
|
|
178
|
+
* file name, while an auxiliary surface passes `outName` and `moduleId` so its
|
|
179
|
+
* bundle lands beside the main one (`lib-client/ball.js`) and registers as
|
|
180
|
+
* `<package>/ball` — the id that surface's window requires.
|
|
181
|
+
*
|
|
182
|
+
* @param packageName - the plugin's npm name (asset URLs and the default module id).
|
|
177
183
|
* @param entry - the renderer entry source file.
|
|
178
184
|
* @param options - see {@link ClientBundleOptions}.
|
|
179
185
|
*/
|
|
@@ -191,7 +197,7 @@ function clientBundle(packageName, entry, options = {}) {
|
|
|
191
197
|
}
|
|
192
198
|
};
|
|
193
199
|
return defineConfig({
|
|
194
|
-
entry: { client: entry },
|
|
200
|
+
entry: { [options.outName ?? "client"]: entry },
|
|
195
201
|
outDir: "lib-client",
|
|
196
202
|
format: ["cjs"],
|
|
197
203
|
platform: "browser",
|
|
@@ -208,7 +214,7 @@ function clientBundle(packageName, entry, options = {}) {
|
|
|
208
214
|
seedImportGuard()
|
|
209
215
|
],
|
|
210
216
|
outputOptions: {
|
|
211
|
-
banner: `window.__ModuleLoader__.load({ id: ${JSON.stringify(packageName)}, factory: function (require, module, exports) {`,
|
|
217
|
+
banner: `window.__ModuleLoader__.load({ id: ${JSON.stringify(options.moduleId ?? packageName)}, factory: function (require, module, exports) {`,
|
|
212
218
|
footer: "return module.exports && module.exports.__esModule === true && \"default\" in module.exports ? module.exports.default : module.exports; } });"
|
|
213
219
|
}
|
|
214
220
|
});
|
package/lib/manifest.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { AUX_SURFACE_IDS } from "@snap-rail/protocol";
|
|
2
3
|
import { PLUGIN_LAYERS } from "@snap-rail/util/manifest";
|
|
3
4
|
//#region lib/types/manifest.js
|
|
4
5
|
/**
|
|
@@ -25,11 +26,29 @@ const seedAbiSchema = z.object({
|
|
|
25
26
|
/** Base-package versions of the seeds the bundle requires, by seed id. */
|
|
26
27
|
versions: z.record(z.string().min(1), z.string().min(1))
|
|
27
28
|
}).strict();
|
|
29
|
+
/**
|
|
30
|
+
* The auxiliary-surface entry map: one client bundle per extra surface the
|
|
31
|
+
* package renders in. Keys are the auxiliary surface ids (`ball`) —
|
|
32
|
+
* `main` is deliberately absent, since the main window's bundle is
|
|
33
|
+
* `client.entry` and a package gets exactly one entry per window. Every
|
|
34
|
+
* declared path is a CJS factory bundle inside the package, packed and
|
|
35
|
+
* validated like the main entry; the surfaces a package does not declare
|
|
36
|
+
* simply have no face and show the kernel's plain fallback.
|
|
37
|
+
*/
|
|
38
|
+
const clientSurfacesSchema = z.partialRecord(z.enum(AUX_SURFACE_IDS), z.string().min(1));
|
|
28
39
|
/** The renderer face declaration: where the client bundle lives. */
|
|
29
40
|
const clientFaceSchema = z.object({
|
|
30
|
-
/** Path of the CJS factory bundle inside the package (from its root)
|
|
41
|
+
/** Path of the CJS factory bundle inside the package (from its root) —
|
|
42
|
+
* the **main** surface's entry. */
|
|
31
43
|
entry: z.string().min(1).default("lib/client.js"),
|
|
32
44
|
/**
|
|
45
|
+
* Extra surfaces this package renders in: one client bundle per auxiliary
|
|
46
|
+
* window (`ball`). Each bundle is loaded into that surface's own
|
|
47
|
+
* window with the same seed table, and its module id is
|
|
48
|
+
* `<package>/<surface>` (the main entry keeps the plain package name).
|
|
49
|
+
*/
|
|
50
|
+
surfaces: clientSurfacesSchema.optional(),
|
|
51
|
+
/**
|
|
33
52
|
* Subpaths this client face registers as contributed seeds (e.g. framework
|
|
34
53
|
* primitives like `./ui`): each becomes a `__ModuleLoader__` id other
|
|
35
54
|
* plugins' client bundles may `require` — same instance, one execution.
|
|
@@ -119,4 +138,4 @@ const snapRailManifestSchema = z.object({
|
|
|
119
138
|
}).strict().optional()
|
|
120
139
|
});
|
|
121
140
|
//#endregion
|
|
122
|
-
export { PLUGIN_KINDS, PLUGIN_LAYERS, clientFaceSchema, seedAbiSchema, snapRailManifestSchema };
|
|
141
|
+
export { PLUGIN_KINDS, PLUGIN_LAYERS, clientFaceSchema, clientSurfacesSchema, seedAbiSchema, snapRailManifestSchema };
|
package/lib/pack.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CLIENT_EXTERNALS } from "./seeds.js";
|
|
2
|
+
import { clientSurfacesSchema } from "./manifest.js";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import { readFile, readdir } from "node:fs/promises";
|
|
4
5
|
import { join, relative } from "node:path";
|
|
@@ -64,22 +65,25 @@ function basePackageOf(seedId) {
|
|
|
64
65
|
return seedId.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
65
66
|
}
|
|
66
67
|
/**
|
|
67
|
-
* Stamp the seed ABI a built client
|
|
68
|
-
*
|
|
69
|
-
* the only bare requires the welded CJS output carries) plus
|
|
70
|
-
* base-package version resolved from the package's own dependency
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
68
|
+
* Stamp the seed ABI a set of built client faces were built against: the
|
|
69
|
+
* seeds their bundles actually require (a `require("…")` literal scan —
|
|
70
|
+
* externals are the only bare requires the welded CJS output carries) plus
|
|
71
|
+
* each seed's base-package version resolved from the package's own dependency
|
|
72
|
+
* tree. A multi-surface package stamps ONE declaration for all its entries
|
|
73
|
+
* (the union of their requires): every surface shares the shell's seed table,
|
|
74
|
+
* so a per-face stamp would only repeat the same verdict N times. Ids whose
|
|
75
|
+
* base package cannot be resolved here are omitted — the runtime check
|
|
76
|
+
* compares only the intersection, so an omitted id is unchecked, never
|
|
77
|
+
* failed. Returns `undefined` when the bundles require no seeds or none could
|
|
78
|
+
* be versioned (nothing to enforce).
|
|
75
79
|
*
|
|
76
80
|
* @param packageDir - the package being packed (version resolution root).
|
|
77
|
-
* @param
|
|
81
|
+
* @param bundleCodes - the built client bundles' source texts, one per surface.
|
|
78
82
|
*/
|
|
79
|
-
async function seedAbiStamp(packageDir,
|
|
83
|
+
async function seedAbiStamp(packageDir, bundleCodes) {
|
|
80
84
|
const seeds = new Set(CLIENT_EXTERNALS);
|
|
81
85
|
const required = /* @__PURE__ */ new Set();
|
|
82
|
-
for (const match of bundleCode.matchAll(/require\(\s*(['"])([^'"\n]+)\1\s*\)/g)) {
|
|
86
|
+
for (const bundleCode of bundleCodes) for (const match of bundleCode.matchAll(/require\(\s*(['"])([^'"\n]+)\1\s*\)/g)) {
|
|
83
87
|
const id = match[2];
|
|
84
88
|
if (id !== void 0 && seeds.has(id)) required.add(id);
|
|
85
89
|
}
|
|
@@ -137,6 +141,12 @@ async function releaseFiles(spec) {
|
|
|
137
141
|
if (spec.hostFace !== void 0 && spec.hostDir === void 0) throw new Error(`pack: ${spec.name} declares a host face without a hostDir`);
|
|
138
142
|
if (spec.hostFace === void 0 && spec.clientFace === void 0) throw new Error(`pack: ${spec.name} ships neither a host face nor a client bundle`);
|
|
139
143
|
const clientEntry = typeof snapRail?.client?.entry === "string" ? snapRail.client.entry : "lib/client.js";
|
|
144
|
+
let clientSurfaces = {};
|
|
145
|
+
if (snapRail?.client?.surfaces !== void 0) {
|
|
146
|
+
const parsed = clientSurfacesSchema.safeParse(snapRail.client.surfaces);
|
|
147
|
+
if (!parsed.success) throw new Error(`pack: ${spec.name} declares an invalid snapRail.client.surfaces (${parsed.error.issues.map((issue) => issue.message).join("; ")})`);
|
|
148
|
+
clientSurfaces = parsed.data;
|
|
149
|
+
}
|
|
140
150
|
const main = spec.hostFace ?? clientEntry;
|
|
141
151
|
const files = {};
|
|
142
152
|
if (spec.hostDir !== void 0) {
|
|
@@ -146,7 +156,13 @@ async function releaseFiles(spec) {
|
|
|
146
156
|
if (spec.clientFace !== void 0) Object.assign(files, await collectDir(join(spec.dir, spec.clientFace), spec.dir));
|
|
147
157
|
if (spec.hostFace !== void 0 && files[spec.hostFace] === void 0) throw new Error(`pack: ${spec.name} is missing its host face ${spec.hostFace} under ${String(spec.hostDir)}`);
|
|
148
158
|
if (declaredClient && files[clientEntry] === void 0) throw new Error(`pack: ${spec.name} declares client entry ${clientEntry} but the zip does not carry it`);
|
|
149
|
-
const
|
|
159
|
+
for (const [surface, entry] of Object.entries(clientSurfaces)) {
|
|
160
|
+
if (entry === void 0) continue;
|
|
161
|
+
if (files[entry] === void 0) throw new Error(`pack: ${spec.name} declares the ${surface} surface entry ${entry} but the zip does not carry it`);
|
|
162
|
+
if (entry === clientEntry) throw new Error(`pack: ${spec.name} points the ${surface} surface at the main client entry ${entry} — each surface needs its own bundle`);
|
|
163
|
+
}
|
|
164
|
+
const clientBundles = [clientEntry, ...Object.values(clientSurfaces)].filter((entry) => typeof entry === "string" && files[entry] !== void 0).map((entry) => new TextDecoder().decode(files[entry]));
|
|
165
|
+
const seedAbi = declaredClient && clientBundles.length > 0 ? await seedAbiStamp(spec.dir, clientBundles) : void 0;
|
|
150
166
|
files["package.json"] = new TextEncoder().encode(releaseManifest(source, main, spec.exports, seedAbi));
|
|
151
167
|
for (const [subpath, target] of Object.entries(spec.exports ?? {})) if (files[target] === void 0) throw new Error(`pack: ${spec.name} exports ${subpath} to ${target} but the zip does not carry it`);
|
|
152
168
|
for (const path of Object.keys(files)) if (path.startsWith("src/")) throw new Error(`pack: ${spec.name} would ship a source file (${path})`);
|
package/lib/seeds.js
CHANGED
package/lib/types/build.d.ts
CHANGED
|
@@ -50,6 +50,21 @@ export interface ClientBundleOptions {
|
|
|
50
50
|
* by the entry, so the bundle carries its own styling.
|
|
51
51
|
*/
|
|
52
52
|
css?: readonly string[] | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* The emitted file's base name inside `lib-client` (default `client`, so
|
|
55
|
+
* the file is `lib-client/client.js`). Auxiliary surfaces build one bundle
|
|
56
|
+
* per window and name them after the surface (`ball`); the
|
|
57
|
+
* manifest's `client.surfaces` points at those files.
|
|
58
|
+
*/
|
|
59
|
+
outName?: string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* The module id the bundle registers under (default: the package name).
|
|
62
|
+
* A package's **main** surface registers as the package itself, so
|
|
63
|
+
* `createClientRuntime` can require it by row name; an auxiliary surface
|
|
64
|
+
* registers as `<package>/<surface>` — one package, several windows, no
|
|
65
|
+
* two bundles fighting over one id.
|
|
66
|
+
*/
|
|
67
|
+
moduleId?: string | undefined;
|
|
53
68
|
}
|
|
54
69
|
/**
|
|
55
70
|
* The client face: a CJS browser bundle welded into the module-loader
|
|
@@ -64,7 +79,13 @@ export interface ClientBundleOptions {
|
|
|
64
79
|
* subpath to its module namespace, and the shell seeds
|
|
65
80
|
* `<package>/<subpath>` ids from that map after requiring the face.
|
|
66
81
|
*
|
|
67
|
-
*
|
|
82
|
+
* A package that renders in more than one window calls this once per surface:
|
|
83
|
+
* the main surface keeps the package name as its module id and `client` as its
|
|
84
|
+
* file name, while an auxiliary surface passes `outName` and `moduleId` so its
|
|
85
|
+
* bundle lands beside the main one (`lib-client/ball.js`) and registers as
|
|
86
|
+
* `<package>/ball` — the id that surface's window requires.
|
|
87
|
+
*
|
|
88
|
+
* @param packageName - the plugin's npm name (asset URLs and the default module id).
|
|
68
89
|
* @param entry - the renderer entry source file.
|
|
69
90
|
* @param options - see {@link ClientBundleOptions}.
|
|
70
91
|
*/
|
package/lib/types/manifest.d.ts
CHANGED
|
@@ -19,9 +19,24 @@ export declare const seedAbiSchema: z.ZodObject<{
|
|
|
19
19
|
revision: z.ZodNumber;
|
|
20
20
|
versions: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
21
21
|
}, z.core.$strict>;
|
|
22
|
+
/**
|
|
23
|
+
* The auxiliary-surface entry map: one client bundle per extra surface the
|
|
24
|
+
* package renders in. Keys are the auxiliary surface ids (`ball`) —
|
|
25
|
+
* `main` is deliberately absent, since the main window's bundle is
|
|
26
|
+
* `client.entry` and a package gets exactly one entry per window. Every
|
|
27
|
+
* declared path is a CJS factory bundle inside the package, packed and
|
|
28
|
+
* validated like the main entry; the surfaces a package does not declare
|
|
29
|
+
* simply have no face and show the kernel's plain fallback.
|
|
30
|
+
*/
|
|
31
|
+
export declare const clientSurfacesSchema: z.ZodRecord<z.ZodEnum<{
|
|
32
|
+
ball: "ball";
|
|
33
|
+
}> & z.core.$partial, z.ZodString>;
|
|
22
34
|
/** The renderer face declaration: where the client bundle lives. */
|
|
23
35
|
export declare const clientFaceSchema: z.ZodObject<{
|
|
24
36
|
entry: z.ZodDefault<z.ZodString>;
|
|
37
|
+
surfaces: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
38
|
+
ball: "ball";
|
|
39
|
+
}> & z.core.$partial, z.ZodString>>;
|
|
25
40
|
seeds: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
41
|
externalSeeds: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
27
42
|
seedAbi: z.ZodOptional<z.ZodObject<{
|
|
@@ -52,6 +67,9 @@ export declare const snapRailManifestSchema: z.ZodObject<{
|
|
|
52
67
|
clientOnly: z.ZodOptional<z.ZodBoolean>;
|
|
53
68
|
client: z.ZodOptional<z.ZodObject<{
|
|
54
69
|
entry: z.ZodDefault<z.ZodString>;
|
|
70
|
+
surfaces: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
71
|
+
ball: "ball";
|
|
72
|
+
}> & z.core.$partial, z.ZodString>>;
|
|
55
73
|
seeds: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
56
74
|
externalSeeds: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
57
75
|
seedAbi: z.ZodOptional<z.ZodObject<{
|
package/lib/types/pack.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snap-rail/plugin-kit",
|
|
3
3
|
"description": "The plugin author's kit: snapRail manifest vocabulary, the renderer seed whitelist, tsdown presets for installable host + client faces, release signing, and the final-format packer",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"fflate": "^0.8.3",
|
|
60
60
|
"zod": "^4.4.3",
|
|
61
|
+
"@snap-rail/protocol": "^0.1.3",
|
|
61
62
|
"@snap-rail/util": "^0.1.2"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|