@snap-rail/plugin-kit 0.0.0-stage → 0.2.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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Final-format assembly: turn a built plugin package into the file set of
3
+ * its installable zip. The zip is **the release artifact, not the source
4
+ * tree** — a trimmed manifest whose `main` names the host face (or, for
5
+ * `clientOnly` packages, the client bundle), the built faces themselves, and
6
+ * an `exports` map covering the subpaths pool-resident importers resolve
7
+ * (`./contract` and friends). The settings page's 安装插件, the CLI packer,
8
+ * the feed publishers, and the offline combo assembler all consume the same
9
+ * shape.
10
+ *
11
+ * @module @snap-rail/plugin-kit/pack
12
+ */
13
+ import { type SeedAbi } from './seeds.ts';
14
+ /** The manifest fields the release manifest carries over from the source. */
15
+ interface SourceManifest {
16
+ name?: unknown;
17
+ version?: unknown;
18
+ description?: unknown;
19
+ snapRail?: {
20
+ clientOnly?: unknown;
21
+ client?: {
22
+ entry?: unknown;
23
+ } | undefined;
24
+ } & Record<string, unknown>;
25
+ }
26
+ /** What one plugin package ships: its host face directory and (optionally)
27
+ * the built client bundle directory (the bundle plus emitted assets). */
28
+ export interface ReleaseSpec {
29
+ /** The package's npm name; must match the source manifest. */
30
+ name: string;
31
+ /** Absolute package directory (the workspace package at pack time). */
32
+ dir: string;
33
+ /** Directory inside the package holding the built host face — included
34
+ * wholesale (multi-entry builds emit shared chunks beside the entries).
35
+ * Declarations, source maps, and tsbuildinfo files never ship. Required
36
+ * with `hostFace`. */
37
+ hostDir?: string | undefined;
38
+ /** Path inside the package of the host face entry — becomes the manifest
39
+ * `main` (the pool mounts exactly one host entry per package). Omitted for
40
+ * `snapRail.clientOnly` packages (renderer-only; `main` names the client
41
+ * bundle instead). */
42
+ hostFace?: string | undefined;
43
+ /** Directory inside the package holding the built client bundle and its
44
+ * assets; required iff the manifest declares `snapRail.client`. */
45
+ clientFace?: string | undefined;
46
+ /** Extra subpath exports the release manifest carries beyond `.` (e.g.
47
+ * `'./contract' → 'lib/contract.js'`), so pool-resident importers and the
48
+ * resolve-hooks pool fallback can bind them. Targets must ship. */
49
+ exports?: Readonly<Record<string, string>> | undefined;
50
+ }
51
+ /**
52
+ * Derive the release manifest: the npm identity plus `main` plus the trimmed
53
+ * `exports` map. Everything else (scripts, dev dependencies) is workspace
54
+ * plumbing the pool never reads and the zip never carries. A stamped seed
55
+ * ABI merges into `snapRail.client` — the build-truth declaration replaces
56
+ * anything a source manifest may have carried.
57
+ *
58
+ * @param source - the parsed source `package.json`.
59
+ * @param main - the entry the manifest `main` names.
60
+ * @param exports - subpath exports beyond `.`; the root voice is always
61
+ * included.
62
+ * @param seedAbi - the pack-time seed-contract stamp for client faces.
63
+ * @returns the manifest serialized for the zip root.
64
+ */
65
+ export declare function releaseManifest(source: SourceManifest, main: string, exports?: Readonly<Record<string, string>>, seedAbi?: SeedAbi | undefined): string;
66
+ /**
67
+ * Assemble one package's release file set: the release manifest, the declared
68
+ * faces, and the exports targets. Fails loud when the source manifest drifts
69
+ * from the spec or a declared face is missing on disk, so a broken build can
70
+ * never ship as a plausible zip.
71
+ *
72
+ * @param spec - what the package ships and from where.
73
+ * @returns the zip's files, keyed by package-relative path.
74
+ */
75
+ export declare function releaseFiles(spec: ReleaseSpec): Promise<Record<string, Uint8Array>>;
76
+ /** The zip file name a package's release takes under dist-plugins/. */
77
+ export declare function zipFileName(packageName: string): string;
78
+ /** One plugin entry the feed's `index.json` carries (feed facts only). */
79
+ export interface FeedEntry {
80
+ name: string;
81
+ version: string;
82
+ description?: string;
83
+ kind?: string;
84
+ title?: string;
85
+ category?: string;
86
+ requires?: string[];
87
+ permissions?: string[];
88
+ engines?: {
89
+ snapRail?: string;
90
+ hostProtocol?: string;
91
+ renderer?: string;
92
+ };
93
+ /** The pure file name the entry advertises (no path). */
94
+ file: string;
95
+ sha256: string;
96
+ size: number;
97
+ signature?: {
98
+ algorithm: 'Ed25519';
99
+ keyId: string;
100
+ };
101
+ repository?: string;
102
+ }
103
+ /**
104
+ * The one feed-entry emitter every producer shares: read a plugin zip's own
105
+ * bytes back (release manifest + signature envelope), derive the
106
+ * `index.json` entry from exactly what the zip carries, and digest the exact
107
+ * bytes the client will download — the feed can never advertise something a
108
+ * zip does not. The repo's pack/collect pipeline and the standalone
109
+ * `snap-rail-plugin feed` command all converge here.
110
+ *
111
+ * @param zipped - the exact zip bytes the feed will serve.
112
+ * @param file - the pure file name the entry advertises (no path).
113
+ * @param fallbackName - the entry's name when the manifest carries none.
114
+ * @param options.requireSignature - refuse unsigned zips (feed policy).
115
+ * @param options.repository - an optional source-repository URL.
116
+ * @returns the entry, unknown fields stripped by construction.
117
+ * @throws when the zip has no readable manifest, or no signature while
118
+ * `requireSignature` is set.
119
+ */
120
+ export declare function catalogEntryFromZip(zipped: Uint8Array, file: string, fallbackName: string, options?: {
121
+ requireSignature?: boolean;
122
+ repository?: string;
123
+ }): FeedEntry;
124
+ export {};
125
+ //# sourceMappingURL=pack.d.ts.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The combo (recipe) document shipped inside an offline combo archive: the
3
+ * exact `plugins.yml` row shape plus the combination's identity. The archive
4
+ * itself is a plain zip — `recipe.yml` at the root, one signed plugin zip per
5
+ * referenced package under `plugins/` — so an offline terminal can install a
6
+ * whole scenario without any feed request.
7
+ *
8
+ * @module @snap-rail/plugin-kit/recipe
9
+ */
10
+ import { z } from 'zod';
11
+ /** One combo row: exactly the user-layer row shape, strict. */
12
+ export declare const recipeRowSchema: z.ZodObject<{
13
+ name: z.ZodString;
14
+ enabled: z.ZodOptional<z.ZodBoolean>;
15
+ config: z.ZodOptional<z.ZodUnknown>;
16
+ }, z.core.$strict>;
17
+ /** A combo package's document: identity + the rows the preset carries. */
18
+ export declare const recipeDocumentSchema: z.ZodObject<{
19
+ id: z.ZodString;
20
+ version: z.ZodString;
21
+ title: z.ZodString;
22
+ kind: z.ZodEnum<{
23
+ base: "base";
24
+ scenario: "scenario";
25
+ }>;
26
+ description: z.ZodOptional<z.ZodString>;
27
+ plugins: z.ZodArray<z.ZodObject<{
28
+ name: z.ZodString;
29
+ enabled: z.ZodOptional<z.ZodBoolean>;
30
+ config: z.ZodOptional<z.ZodUnknown>;
31
+ }, z.core.$strict>>;
32
+ }, z.core.$strict>;
33
+ /** One parsed combo row. */
34
+ export type RecipeRow = z.output<typeof recipeRowSchema>;
35
+ /** One parsed combo document. */
36
+ export type RecipeDocument = z.output<typeof recipeDocumentSchema>;
37
+ /**
38
+ * The real package names a combo references, deduplicated in document order.
39
+ * A subpath row (`@snap-rail/equipment/rpc`) addresses the package
40
+ * `@snap-rail/equipment`; the combo archive carries one zip per package.
41
+ *
42
+ * @param document - the parsed combo document.
43
+ * @returns unique package names in first-reference order.
44
+ */
45
+ export declare function recipePackageNames(document: RecipeDocument): string[];
46
+ /**
47
+ * The npm package a row belongs to (`@scope/pkg/sub` → `@scope/pkg`).
48
+ *
49
+ * @param name - a package name or subpath entry.
50
+ * @returns the package name.
51
+ */
52
+ export declare function packageNameOf(name: string): string;
53
+ //# sourceMappingURL=recipe.d.ts.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The renderer's shared-module vocabulary, in two tiers.
3
+ *
4
+ * `SEED_MODULES` is the **base tier**: the ids the shell itself seeds (by
5
+ * importing them statically into the app bundle) and parity-checks at boot.
6
+ * Everything a client bundle `require`s beyond this list must either be
7
+ * bundled in or contributed (below). Cross-plugin value imports are
8
+ * forbidden (collaboration goes through cordis services and slots); spine
9
+ * contract faces (`./contract` — zod + types only) and pure utility faces
10
+ * qualify; package host entries never do — the node side (store, drizzle,
11
+ * node:sqlite) must stay out of the client graph. Adding a base id is a
12
+ * spine decision.
13
+ *
14
+ * `CONTRIBUTED_SEED_MODULES` is the **installed tier**: ids that arrive with
15
+ * an installed plugin's client face (the framework's primitives, an
16
+ * extension pack's components). The provider's client entry default-exports
17
+ * a `seeds` map (`{ seeds: { ui: namespace, … } }`); the shell requires the
18
+ * face once and seeds every `<package>/<key>` id from that map, so every
19
+ * consumer shares the provider's single instances. The shell never seeds
20
+ * these itself — an absent provider simply leaves the ids unseeded and
21
+ * dependent faces skip with a load error instead of mounting half a UI.
22
+ *
23
+ * `CLIENT_EXTERNALS` (the union) is the external surface client bundles
24
+ * build against: the plugin-kit's client preset externalizes exactly these
25
+ * ids.
26
+ *
27
+ * @module @snap-rail/plugin-kit/seeds
28
+ */
29
+ /** Module ids the shell seeds and every client bundle may require. */
30
+ export declare const SEED_MODULES: readonly string[];
31
+ /**
32
+ * Module ids installed plugins contribute through their client face's
33
+ * `seeds` map — the framework tier: every face of an installed UI framework
34
+ * (primitives, slot registry, runtime, fallback) serves as a shared
35
+ * instance for the components bound to it. The equipment wire contract
36
+ * rides the base tier instead — it is the station platform's API surface,
37
+ * not an installed framework's; a scenario's own contract stays inside its
38
+ * suite package. A third-party framework contributes its own ids; this
39
+ * list covers the in-repo builds.
40
+ */
41
+ export declare const CONTRIBUTED_SEED_MODULES: readonly string[];
42
+ /** The full external surface for in-repo client-bundle builds. */
43
+ export declare const CLIENT_EXTERNALS: readonly string[];
44
+ /**
45
+ * The seed contract's ABI revision. Bump when the contract between the
46
+ * shell's seed table and client bundles changes shape — interop semantics,
47
+ * a seed id's export shape. Packs stamp every client face with the revision
48
+ * it was built against (`snapRail.client.seedAbi`); the shell refuses a
49
+ * face stamped with a foreign revision instead of letting it crash the
50
+ * renderer (r1 exists because a seed default-import compiled to a namespace
51
+ * where the author meant a class — the shape must be checkable).
52
+ */
53
+ export declare const SEED_ABI_REVISION = 1;
54
+ /** What a client face declares about the seed table it was built against. */
55
+ export interface SeedAbi {
56
+ /** The contract revision the face was built against. */
57
+ revision: number;
58
+ /** Base-package versions of the seeds the bundle requires, by seed id. */
59
+ versions: Record<string, string>;
60
+ }
61
+ /** The verdict of comparing a face's declaration against the live table. */
62
+ export interface SeedAbiVerdict {
63
+ /** Whether the face may load. */
64
+ ok: boolean;
65
+ /** The face carries no declaration — the grace-window pass. */
66
+ undeclared: boolean;
67
+ /** Human-readable refusal reason; absent when `ok`. */
68
+ reason?: string;
69
+ }
70
+ /**
71
+ * Compare a client face's stamped seed ABI against the live table. Only the
72
+ * ids present in BOTH maps are compared (a stamp covers exactly what its
73
+ * build could resolve): the revision must match exactly, every shared
74
+ * version must agree on the major. An absent declaration passes — zips
75
+ * predating the stamping stay installable, marked for the caller to log.
76
+ */
77
+ export declare function checkSeedAbi(declared: SeedAbi | undefined, host: SeedAbi): SeedAbiVerdict;
78
+ //# sourceMappingURL=seeds.d.ts.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Release metadata and Ed25519 signatures for installable plugins.
3
+ *
4
+ * The signature covers a canonical release manifest. The manifest contains a
5
+ * digest for every payload file, so the verifier authenticates both the
6
+ * publisher and the bytes that will be installed without signing the zip
7
+ * container itself.
8
+ *
9
+ * @module @snap-rail/plugin-kit/sign
10
+ */
11
+ import { type KeyObject } from 'node:crypto';
12
+ /** The metadata file embedded in a release archive. */
13
+ export declare const RELEASE_METADATA_PATH = "META-INF/release.json";
14
+ /** The signature envelope embedded in a release archive. */
15
+ export declare const RELEASE_SIGNATURE_PATH = "META-INF/signature.json";
16
+ /** A release manifest's file digest table. */
17
+ export interface ReleaseFileDigests {
18
+ readonly [path: string]: string;
19
+ }
20
+ /** Signed metadata carried by one plugin release. */
21
+ export interface ReleaseMetadata {
22
+ format: 1;
23
+ name: string;
24
+ version: string;
25
+ publisher?: string;
26
+ createdAt: string;
27
+ files: ReleaseFileDigests;
28
+ }
29
+ /** The detached signature envelope carried by one plugin release. */
30
+ export interface ReleaseSignature {
31
+ algorithm: 'Ed25519';
32
+ keyId: string;
33
+ signedFile: typeof RELEASE_METADATA_PATH;
34
+ signature: string;
35
+ }
36
+ /** A trusted public key indexed by the publisher's stable key id. */
37
+ export interface TrustedReleaseKey {
38
+ keyId: string;
39
+ publicKey: KeyObject | string | Uint8Array;
40
+ publisher?: string;
41
+ }
42
+ /** Stable JSON serialization used as the Ed25519 signing input. */
43
+ export declare function canonicalJson(value: unknown): string;
44
+ /** SHA-256 digest of one payload file. */
45
+ export declare function sha256(data: Uint8Array): string;
46
+ /**
47
+ * Build signed metadata from a release file set. Metadata and signature files
48
+ * are excluded from the digest table so signing never becomes recursive.
49
+ */
50
+ export declare function releaseMetadata(files: Readonly<Record<string, Uint8Array>>, options: {
51
+ name: string;
52
+ version: string;
53
+ publisher?: string;
54
+ createdAt?: string;
55
+ }): ReleaseMetadata;
56
+ /** Sign release metadata with an Ed25519 private key. */
57
+ export declare function signReleaseMetadata(metadata: ReleaseMetadata, key: {
58
+ keyId: string;
59
+ privateKey: KeyObject | string | Uint8Array;
60
+ }): ReleaseSignature;
61
+ /** Add signed metadata and signature files to a release file set. */
62
+ export declare function signReleaseFiles(files: Readonly<Record<string, Uint8Array>>, options: {
63
+ name: string;
64
+ version: string;
65
+ keyId: string;
66
+ privateKey: KeyObject | string | Uint8Array;
67
+ publisher?: string;
68
+ createdAt?: string;
69
+ }): Record<string, Uint8Array>;
70
+ /** Verify release metadata, signature and every payload file digest. */
71
+ export declare function verifyReleaseFiles(files: Readonly<Record<string, Uint8Array>>, trustedKeys: readonly TrustedReleaseKey[]): ReleaseMetadata;
72
+ //# sourceMappingURL=sign.d.ts.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Authoring helper for UI framework packages: define the package's slot
3
+ * vocabulary in one place and get a runtime membership guard for it. A
4
+ * vocabulary is plain data — a `shell.menu`-style dotted id table plus the
5
+ * reserved `custom:<publisher>.<name>` extension namespace — and every
6
+ * consumer (components, layouts) checks membership through the returned
7
+ * guard instead of hand-rolled string tests. Ids are declared with an
8
+ * `as const` object so consumers get autocomplete and compile-time
9
+ * misspelling errors from the literal-union type (`SlotIdOf`).
10
+ *
11
+ * The shipped framework (@snap-rail/framework-shadcn) implements the same
12
+ * contract inline (`UI_SLOT_IDS`/`isValidUiSlotId`); new frameworks start
13
+ * here.
14
+ *
15
+ * @module @snap-rail/plugin-kit/slot-vocabulary
16
+ */
17
+ /** The reserved extension namespace: a custom slot is owned by a publisher. */
18
+ export declare const CUSTOM_SLOT_PATTERN: RegExp;
19
+ /** The slot id union of a vocabulary: its declared ids plus `custom:` seats. */
20
+ export type SlotIdOf<T extends Record<string, string>> = T[keyof T] | `custom:${string}`;
21
+ export interface SlotVocabulary<T extends Record<string, string>> {
22
+ /** The frozen id table (e.g. `{ menu: 'shell.menu', titlebar: 'shell.titlebar' }`). */
23
+ readonly ids: T;
24
+ /** Membership guard: the declared vocabulary plus `custom:` seats. */
25
+ isSlotId(value: string): value is SlotIdOf<T>;
26
+ }
27
+ /** Define and validate a framework's slot vocabulary. Malformed ids throw at
28
+ * definition time — a vocabulary ships once and is consumed everywhere, so
29
+ * it fails loudly in the framework author's build, never in a component. */
30
+ export declare function defineSlotVocabulary<const T extends Record<string, string>>(ids: T, options?: {
31
+ customPattern?: RegExp;
32
+ }): SlotVocabulary<T>;
33
+ //# sourceMappingURL=slot-vocabulary.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,73 @@
1
1
  {
2
2
  "name": "@snap-rail/plugin-kit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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.1",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "homepage": "https://www.snap-rail.top/dev.html",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/yansijian/snap-rail.git",
12
+ "directory": "packages/base/plugin-kit"
13
+ },
14
+ "keywords": [
15
+ "snap-rail",
16
+ "plugin",
17
+ "cordis",
18
+ "industrial",
19
+ "electron"
20
+ ],
21
+ "exports": {
22
+ ".": {
23
+ "types": "./lib/types/index.d.ts",
24
+ "default": "./lib/index.js"
25
+ },
26
+ "./manifest": {
27
+ "types": "./lib/types/manifest.d.ts",
28
+ "default": "./lib/manifest.js"
29
+ },
30
+ "./seeds": {
31
+ "types": "./lib/types/seeds.d.ts",
32
+ "default": "./lib/seeds.js"
33
+ },
34
+ "./build": {
35
+ "types": "./lib/types/build.d.ts",
36
+ "default": "./lib/build.js"
37
+ },
38
+ "./pack": {
39
+ "types": "./lib/types/pack.d.ts",
40
+ "default": "./lib/pack.js"
41
+ },
42
+ "./sign": {
43
+ "types": "./lib/types/sign.d.ts",
44
+ "default": "./lib/sign.js"
45
+ },
46
+ "./recipe": {
47
+ "types": "./lib/types/recipe.d.ts",
48
+ "default": "./lib/recipe.js"
49
+ },
50
+ "./src/*": "./src/*",
51
+ "./package.json": "./package.json"
52
+ },
53
+ "files": [
54
+ "lib/*.js",
55
+ "lib/types/**/*.d.ts"
56
+ ],
57
+ "license": "Apache-2.0",
58
+ "dependencies": {
59
+ "fflate": "^0.8.3",
60
+ "zod": "^4.4.3",
61
+ "@snap-rail/util": "^0.1.2"
62
+ },
63
+ "peerDependencies": {
64
+ "tsdown": "^0.22.2"
65
+ },
66
+ "devDependencies": {
67
+ "rolldown": "^1.2.5",
68
+ "tsdown": "^0.22.2"
69
+ },
70
+ "snapRail": {
71
+ "layer": "base"
72
+ }
6
73
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.