@openvcs/sdk 0.2.2 → 0.2.3

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/README.md CHANGED
@@ -44,6 +44,7 @@ Run the local CLI through npm scripts:
44
44
 
45
45
  ```bash
46
46
  npm run openvcs -- --help
47
+ npm run openvcs -- build --help
47
48
  npm run openvcs -- init --help
48
49
  npm run openvcs -- dist --help
49
50
  ```
@@ -66,23 +67,60 @@ Interactive theme plugin scaffold:
66
67
  openvcs init --theme my-theme
67
68
  ```
68
69
 
70
+ ## Build plugin assets
71
+
72
+ In a generated code plugin folder:
73
+
74
+ ```bash
75
+ npm run build
76
+ ```
77
+
78
+ This runs `openvcs build`, which executes `scripts["build:plugin"]` and verifies
79
+ that `bin/<module.exec>` now exists.
80
+
81
+ Theme-only plugins can also run `npm run build`; the command exits successfully
82
+ without producing `bin/` output.
83
+
69
84
  ## Build a `.ovcsp` bundle
70
85
 
71
86
  In a generated plugin folder:
72
87
 
73
88
  ```bash
74
- npm run build
89
+ npm run dist
75
90
  ```
76
91
 
77
92
  This produces `dist/<plugin-id>.ovcsp`.
78
93
 
94
+ `openvcs dist` runs `openvcs build` first unless `--no-build` is provided.
95
+ Use `--no-build` when packaging prebuilt plugin assets.
96
+
97
+ Generated code plugin scripts use this split by default:
98
+
99
+ ```json
100
+ {
101
+ "scripts": {
102
+ "build:plugin": "tsc -p tsconfig.json",
103
+ "build": "openvcs build",
104
+ "dist": "openvcs dist --plugin-dir . --out dist"
105
+ }
106
+ }
107
+ ```
108
+
79
109
  `.ovcsp` is a gzip-compressed tar archive (`tar.gz`) that contains a top-level
80
110
  `<plugin-id>/` directory with `openvcs.plugin.json` and plugin runtime assets.
81
111
 
112
+ Bundle contents:
113
+ - `openvcs.plugin.json` (required)
114
+ - `icon.*` (optional, first found by extension priority)
115
+ - `bin/` (required for code plugins with `module.exec`)
116
+ - `entry` directory (required for UI plugins with top-level `entry` field; the entire directory containing the entry file is bundled)
117
+ - `themes/` (required for theme plugins)
118
+ - `node_modules/` (if npm dependencies are bundled)
119
+
82
120
  Dependency behavior while packaging:
83
121
 
84
122
  - npm dependency bundling is enabled by default when `package.json` exists.
85
- - If `package-lock.json` is missing, SDK generates it in the plugin worktree.
123
+ - If `package-lock.json` is missing, SDK generates it in the staging area (not the plugin worktree).
86
124
  - Dependencies are installed into the bundle staging dir with:
87
125
  - `npm ci --omit=dev --ignore-scripts --no-bin-links --no-audit --no-fund`
88
126
  - Disable npm dependency processing with `--no-npm-deps`.
@@ -93,15 +131,17 @@ Dependency behavior while packaging:
93
131
  Package a plugin manually:
94
132
 
95
133
  ```bash
96
- openvcs dist --plugin-dir /path/to/plugin --out /path/to/dist
134
+ npx openvcs build --plugin-dir /path/to/plugin
135
+ npx openvcs dist --plugin-dir /path/to/plugin --out /path/to/dist
97
136
  ```
98
137
 
99
138
  Show command help:
100
139
 
101
140
  ```bash
102
- openvcs --help
103
- openvcs dist --help
104
- openvcs init --help
141
+ npx openvcs --help
142
+ npx openvcs build --help
143
+ npx openvcs dist --help
144
+ npx openvcs init --help
105
145
  ```
106
146
 
107
147
  ## Releases
package/lib/build.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /** CLI arguments for `openvcs build`. */
2
+ export interface BuildArgs {
3
+ pluginDir: string;
4
+ verbose: boolean;
5
+ }
6
+ /** Trimmed manifest details used by build and dist flows. */
7
+ export interface ManifestInfo {
8
+ pluginId: string;
9
+ moduleExec: string | undefined;
10
+ entry: string | undefined;
11
+ manifestPath: string;
12
+ }
13
+ /** Returns the npm executable name for the current platform. */
14
+ export declare function npmExecutable(): string;
15
+ /** Formats help text for the build command. */
16
+ export declare function buildUsage(commandName?: string): string;
17
+ /** Parses `openvcs build` arguments. */
18
+ export declare function parseBuildArgs(args: string[]): BuildArgs;
19
+ /** Reads and validates the plugin manifest. */
20
+ export declare function readManifest(pluginDir: string): ManifestInfo;
21
+ /** Verifies that a declared module entry resolves to a real file under `bin/`. */
22
+ export declare function validateDeclaredModuleExec(pluginDir: string, moduleExec: string | undefined): void;
23
+ /** Returns whether the plugin repository has a `package.json`. */
24
+ export declare function hasPackageJson(pluginDir: string): boolean;
25
+ /** Runs a command in the given directory with optional verbose logging. */
26
+ export declare function runCommand(program: string, args: string[], cwd: string, verbose: boolean): void;
27
+ /** Builds a plugin's runtime assets when it declares a code module. */
28
+ export declare function buildPluginAssets(parsedArgs: BuildArgs): ManifestInfo;
package/lib/build.js ADDED
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ // Copyright © 2025-2026 OpenVCS Contributors
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.npmExecutable = npmExecutable;
6
+ exports.buildUsage = buildUsage;
7
+ exports.parseBuildArgs = parseBuildArgs;
8
+ exports.readManifest = readManifest;
9
+ exports.validateDeclaredModuleExec = validateDeclaredModuleExec;
10
+ exports.hasPackageJson = hasPackageJson;
11
+ exports.runCommand = runCommand;
12
+ exports.buildPluginAssets = buildPluginAssets;
13
+ const fs = require("node:fs");
14
+ const path = require("node:path");
15
+ const node_child_process_1 = require("node:child_process");
16
+ const fs_utils_1 = require("./fs-utils");
17
+ /** Returns the npm executable name for the current platform. */
18
+ function npmExecutable() {
19
+ return process.platform === "win32" ? "npm.cmd" : "npm";
20
+ }
21
+ /** Formats help text for the build command. */
22
+ function buildUsage(commandName = "openvcs") {
23
+ return `${commandName} build [args]\n\n --plugin-dir <path> Plugin repository root (contains openvcs.plugin.json)\n -V, --verbose Enable verbose output\n`;
24
+ }
25
+ /** Parses `openvcs build` arguments. */
26
+ function parseBuildArgs(args) {
27
+ let pluginDir = process.cwd();
28
+ let verbose = false;
29
+ for (let index = 0; index < args.length; index += 1) {
30
+ const arg = args[index];
31
+ if (arg === "--plugin-dir") {
32
+ index += 1;
33
+ if (index >= args.length) {
34
+ throw new Error("missing value for --plugin-dir");
35
+ }
36
+ pluginDir = args[index];
37
+ continue;
38
+ }
39
+ if (arg === "-V" || arg === "--verbose") {
40
+ verbose = true;
41
+ continue;
42
+ }
43
+ if (arg === "--help") {
44
+ const error = new Error(buildUsage());
45
+ error.code = "USAGE";
46
+ throw error;
47
+ }
48
+ throw new Error(`unknown flag: ${arg}`);
49
+ }
50
+ return {
51
+ pluginDir: path.resolve(pluginDir),
52
+ verbose,
53
+ };
54
+ }
55
+ /** Reads and validates the plugin manifest. */
56
+ function readManifest(pluginDir) {
57
+ const manifestPath = path.join(pluginDir, "openvcs.plugin.json");
58
+ let manifestRaw;
59
+ let manifestFd;
60
+ let manifest;
61
+ try {
62
+ manifestFd = fs.openSync(manifestPath, "r");
63
+ const manifestStat = fs.fstatSync(manifestFd);
64
+ if (!manifestStat.isFile()) {
65
+ throw new Error(`missing openvcs.plugin.json at ${manifestPath}`);
66
+ }
67
+ manifestRaw = fs.readFileSync(manifestFd, "utf8");
68
+ }
69
+ catch (error) {
70
+ if (error.code === "ENOENT") {
71
+ throw new Error(`missing openvcs.plugin.json at ${manifestPath}`);
72
+ }
73
+ throw error;
74
+ }
75
+ finally {
76
+ if (typeof manifestFd === "number") {
77
+ fs.closeSync(manifestFd);
78
+ }
79
+ }
80
+ try {
81
+ manifest = JSON.parse(manifestRaw);
82
+ }
83
+ catch (error) {
84
+ const detail = error instanceof Error ? error.message : String(error);
85
+ throw new Error(`parse ${manifestPath}: ${detail}`);
86
+ }
87
+ const pluginId = typeof manifest.id === "string"
88
+ ? manifest.id.trim()
89
+ : "";
90
+ if (!pluginId) {
91
+ throw new Error(`manifest ${manifestPath} is missing a string 'id'`);
92
+ }
93
+ if (pluginId === "." || pluginId === ".." || pluginId.includes("/") || pluginId.includes("\\")) {
94
+ throw new Error(`manifest id must not contain path separators: ${pluginId}`);
95
+ }
96
+ const moduleValue = manifest.module;
97
+ const moduleExec = typeof moduleValue?.exec === "string" ? moduleValue.exec.trim() : undefined;
98
+ const entryValue = manifest.entry;
99
+ const entry = typeof entryValue === "string" ? entryValue.trim() : undefined;
100
+ return {
101
+ pluginId,
102
+ moduleExec,
103
+ entry,
104
+ manifestPath,
105
+ };
106
+ }
107
+ /** Verifies that a declared module entry resolves to a real file under `bin/`. */
108
+ function validateDeclaredModuleExec(pluginDir, moduleExec) {
109
+ if (!moduleExec) {
110
+ return;
111
+ }
112
+ const normalizedExec = moduleExec.trim();
113
+ const lowered = normalizedExec.toLowerCase();
114
+ if (!lowered.endsWith(".js") && !lowered.endsWith(".mjs") && !lowered.endsWith(".cjs")) {
115
+ throw new Error(`manifest exec must end with .js/.mjs/.cjs (Node runtime): ${moduleExec}`);
116
+ }
117
+ if (path.isAbsolute(normalizedExec)) {
118
+ throw new Error(`manifest module.exec must be a relative path under bin/: ${moduleExec}`);
119
+ }
120
+ const binDir = path.resolve(pluginDir, "bin");
121
+ const targetPath = path.resolve(binDir, normalizedExec);
122
+ if (!(0, fs_utils_1.isPathInside)(binDir, targetPath) || targetPath === binDir) {
123
+ throw new Error(`manifest module.exec must point to a file under bin/: ${moduleExec}`);
124
+ }
125
+ if (!fs.existsSync(targetPath) || !fs.lstatSync(targetPath).isFile()) {
126
+ throw new Error(`module entrypoint not found at ${targetPath}`);
127
+ }
128
+ }
129
+ /** Returns whether the plugin repository has a `package.json`. */
130
+ function hasPackageJson(pluginDir) {
131
+ const packageJsonPath = path.join(pluginDir, "package.json");
132
+ return fs.existsSync(packageJsonPath) && fs.lstatSync(packageJsonPath).isFile();
133
+ }
134
+ /** Runs a command in the given directory with optional verbose logging. */
135
+ function runCommand(program, args, cwd, verbose) {
136
+ if (verbose) {
137
+ process.stderr.write(`Running command in ${cwd}: ${program} ${args.join(" ")}\n`);
138
+ }
139
+ const result = (0, node_child_process_1.spawnSync)(program, args, {
140
+ cwd,
141
+ stdio: ["ignore", verbose ? "inherit" : "ignore", "inherit"],
142
+ });
143
+ if (result.error) {
144
+ throw new Error(`failed to spawn '${program}' in ${cwd}: ${result.error.message}`);
145
+ }
146
+ if (result.status === 0) {
147
+ return;
148
+ }
149
+ throw new Error(`command failed (${program} ${args.join(" ")}), exit code ${result.status}`);
150
+ }
151
+ /** Reads `package.json` scripts for the plugin, if present. */
152
+ function readPackageScripts(pluginDir) {
153
+ const packageJsonPath = path.join(pluginDir, "package.json");
154
+ let packageData;
155
+ try {
156
+ packageData = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
157
+ }
158
+ catch (error) {
159
+ if (error.code === "ENOENT") {
160
+ throw new Error(`code plugins must include package.json: ${packageJsonPath}`);
161
+ }
162
+ const detail = error instanceof Error ? error.message : String(error);
163
+ throw new Error(`parse ${packageJsonPath}: ${detail}`);
164
+ }
165
+ const scripts = packageData.scripts;
166
+ if (typeof scripts !== "object" || scripts === null) {
167
+ return {};
168
+ }
169
+ return scripts;
170
+ }
171
+ /** Builds a plugin's runtime assets when it declares a code module. */
172
+ function buildPluginAssets(parsedArgs) {
173
+ const manifest = readManifest(parsedArgs.pluginDir);
174
+ if (!manifest.moduleExec) {
175
+ return manifest;
176
+ }
177
+ if (!hasPackageJson(parsedArgs.pluginDir)) {
178
+ throw new Error(`code plugins must include package.json: ${path.join(parsedArgs.pluginDir, "package.json")}`);
179
+ }
180
+ const scripts = readPackageScripts(parsedArgs.pluginDir);
181
+ const buildScript = scripts["build:plugin"];
182
+ if (typeof buildScript !== "string" || buildScript.trim() === "") {
183
+ throw new Error(`code plugins must define scripts[\"build:plugin\"] in ${path.join(parsedArgs.pluginDir, "package.json")}`);
184
+ }
185
+ runCommand(npmExecutable(), ["run", "build:plugin"], parsedArgs.pluginDir, parsedArgs.verbose);
186
+ validateDeclaredModuleExec(parsedArgs.pluginDir, manifest.moduleExec);
187
+ return manifest;
188
+ }
package/lib/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runCli = runCli;
4
+ const build_1 = require("./build");
4
5
  const dist_1 = require("./dist");
5
6
  const init_1 = require("./init");
6
7
  const packageJson = require("../package.json");
@@ -11,7 +12,7 @@ function hasCode(error, code) {
11
12
  error.code === code);
12
13
  }
13
14
  function usage() {
14
- return "Usage: openvcs <command> [options]\n\nCommands:\n dist [args] Package plugin into .ovcsp\n init [--theme] [dir] Interactively scaffold a plugin project\n -v, --version Show version information\n\nDist args:\n --plugin-dir <path> Plugin root containing openvcs.plugin.json\n --out <path> Output directory (default: ./dist)\n --no-npm-deps Skip npm dependency bundling\n -V, --verbose Verbose output\n";
15
+ return "Usage: openvcs <command> [options]\n\nCommands:\n build [args] Build plugin runtime assets\n dist [args] Package plugin into .ovcsp\n init [--theme] [dir] Interactively scaffold a plugin project\n -v, --version Show version information\n\nBuild args:\n --plugin-dir <path> Plugin root containing openvcs.plugin.json\n -V, --verbose Verbose output\n\nDist args:\n --plugin-dir <path> Plugin root containing openvcs.plugin.json\n --out <path> Output directory (default: ./dist)\n --no-build Skip plugin build before packaging\n --no-npm-deps Skip npm dependency bundling\n -V, --verbose Verbose output\n";
15
16
  }
16
17
  async function runCli(args) {
17
18
  if (args.length === 0) {
@@ -19,7 +20,7 @@ async function runCli(args) {
19
20
  process.exitCode = 1;
20
21
  return;
21
22
  }
22
- if (args.includes("-v") || args.includes("--version")) {
23
+ if (args[0] === "-v" || args[0] === "--version") {
23
24
  process.stdout.write(`openvcs ${packageJson.version}\n`);
24
25
  return;
25
26
  }
@@ -46,6 +47,24 @@ async function runCli(args) {
46
47
  throw error;
47
48
  }
48
49
  }
50
+ if (command === "build") {
51
+ if (rest.includes("--help")) {
52
+ process.stdout.write((0, build_1.buildUsage)());
53
+ return;
54
+ }
55
+ try {
56
+ const parsed = (0, build_1.parseBuildArgs)(rest);
57
+ const manifest = (0, build_1.buildPluginAssets)(parsed);
58
+ process.stdout.write(`${manifest.pluginId}\n`);
59
+ return;
60
+ }
61
+ catch (error) {
62
+ if (hasCode(error, "USAGE")) {
63
+ throw new Error((0, build_1.buildUsage)());
64
+ }
65
+ throw error;
66
+ }
67
+ }
49
68
  if (command === "init") {
50
69
  if (rest.includes("--help")) {
51
70
  process.stdout.write((0, init_1.initUsage)());
package/lib/dist.d.ts CHANGED
@@ -1,18 +1,14 @@
1
+ import { readManifest, validateDeclaredModuleExec } from "./build";
1
2
  interface DistArgs {
2
3
  pluginDir: string;
3
4
  outDir: string;
4
5
  verbose: boolean;
5
6
  noNpmDeps: boolean;
6
- }
7
- interface ManifestInfo {
8
- pluginId: string;
9
- moduleExec: string | undefined;
10
- manifestPath: string;
7
+ noBuild: boolean;
11
8
  }
12
9
  export declare function distUsage(commandName?: string): string;
13
10
  export declare function parseDistArgs(args: string[]): DistArgs;
14
- declare function readManifest(pluginDir: string): ManifestInfo;
15
- declare function validateDeclaredModuleExec(pluginDir: string, moduleExec: string | undefined): void;
11
+ declare function validateManifestEntry(pluginDir: string, entry: string): void;
16
12
  declare function rejectNativeAddonsRecursive(dirPath: string): void;
17
13
  declare function copyIcon(pluginDir: string, bundleDir: string): void;
18
14
  declare function uniqueStagingDir(outDir: string): string;
@@ -25,6 +21,7 @@ export declare const __private: {
25
21
  rejectNativeAddonsRecursive: typeof rejectNativeAddonsRecursive;
26
22
  uniqueStagingDir: typeof uniqueStagingDir;
27
23
  validateDeclaredModuleExec: typeof validateDeclaredModuleExec;
24
+ validateManifestEntry: typeof validateManifestEntry;
28
25
  writeTarGz: typeof writeTarGz;
29
26
  };
30
27
  export {};
package/lib/dist.js CHANGED
@@ -6,21 +6,19 @@ exports.parseDistArgs = parseDistArgs;
6
6
  exports.bundlePlugin = bundlePlugin;
7
7
  const fs = require("node:fs");
8
8
  const path = require("node:path");
9
- const node_child_process_1 = require("node:child_process");
10
9
  const tar = require("tar");
10
+ const build_1 = require("./build");
11
11
  const fs_utils_1 = require("./fs-utils");
12
12
  const ICON_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "avif", "svg"];
13
- function npmExecutable() {
14
- return process.platform === "win32" ? "npm.cmd" : "npm";
15
- }
16
13
  function distUsage(commandName = "openvcs") {
17
- return `${commandName} dist [args]\n\n --plugin-dir <path> Plugin repository root (contains openvcs.plugin.json)\n --out <path> Output directory (default: ./dist)\n --no-npm-deps Disable npm dependency bundling (enabled by default)\n -V, --verbose Enable verbose output\n`;
14
+ return `${commandName} dist [args]\n\n --plugin-dir <path> Plugin repository root (contains openvcs.plugin.json)\n --out <path> Output directory (default: ./dist)\n --no-build Skip the plugin build step before packaging\n --no-npm-deps Disable npm dependency bundling (enabled by default)\n -V, --verbose Enable verbose output\n`;
18
15
  }
19
16
  function parseDistArgs(args) {
20
17
  let pluginDir = process.cwd();
21
18
  let outDir = "dist";
22
19
  let verbose = false;
23
20
  let noNpmDeps = false;
21
+ let noBuild = false;
24
22
  for (let index = 0; index < args.length; index += 1) {
25
23
  const arg = args[index];
26
24
  if (arg === "--plugin-dir") {
@@ -43,6 +41,10 @@ function parseDistArgs(args) {
43
41
  noNpmDeps = true;
44
42
  continue;
45
43
  }
44
+ if (arg === "--no-build") {
45
+ noBuild = true;
46
+ continue;
47
+ }
46
48
  if (arg === "-V" || arg === "--verbose") {
47
49
  verbose = true;
48
50
  continue;
@@ -59,109 +61,44 @@ function parseDistArgs(args) {
59
61
  outDir: path.resolve(outDir),
60
62
  verbose,
61
63
  noNpmDeps,
64
+ noBuild,
62
65
  };
63
66
  }
64
- function readManifest(pluginDir) {
65
- const manifestPath = path.join(pluginDir, "openvcs.plugin.json");
66
- let manifestRaw;
67
- let manifestFd;
68
- let manifest;
69
- try {
70
- manifestFd = fs.openSync(manifestPath, "r");
71
- const manifestStat = fs.fstatSync(manifestFd);
72
- if (!manifestStat.isFile()) {
73
- throw new Error(`missing openvcs.plugin.json at ${manifestPath}`);
74
- }
75
- manifestRaw = fs.readFileSync(manifestFd, "utf8");
76
- }
77
- catch (error) {
78
- if (error.code === "ENOENT") {
79
- throw new Error(`missing openvcs.plugin.json at ${manifestPath}`);
80
- }
81
- throw error;
82
- }
83
- finally {
84
- if (typeof manifestFd === "number") {
85
- fs.closeSync(manifestFd);
86
- }
87
- }
88
- try {
89
- manifest = JSON.parse(manifestRaw);
90
- }
91
- catch (error) {
92
- const detail = error instanceof Error ? error.message : String(error);
93
- throw new Error(`parse ${manifestPath}: ${detail}`);
94
- }
95
- const pluginId = typeof manifest.id === "string"
96
- ? manifest.id.trim()
97
- : "";
98
- if (!pluginId) {
99
- throw new Error(`manifest ${manifestPath} is missing a string 'id'`);
100
- }
101
- if (pluginId === "." || pluginId === ".." || pluginId.includes("/") || pluginId.includes("\\")) {
102
- throw new Error(`manifest id must not contain path separators: ${pluginId}`);
103
- }
104
- const moduleValue = manifest.module;
105
- const moduleExec = typeof moduleValue?.exec === "string" ? moduleValue.exec.trim() : undefined;
106
- return {
107
- pluginId,
108
- moduleExec,
109
- manifestPath,
110
- };
111
- }
112
- function validateDeclaredModuleExec(pluginDir, moduleExec) {
113
- if (!moduleExec) {
114
- return;
115
- }
116
- const normalizedExec = moduleExec.trim();
117
- const lowered = normalizedExec.toLowerCase();
118
- if (!lowered.endsWith(".js") && !lowered.endsWith(".mjs") && !lowered.endsWith(".cjs")) {
119
- throw new Error(`manifest exec must end with .js/.mjs/.cjs (Node runtime): ${moduleExec}`);
67
+ function validateManifestEntry(pluginDir, entry) {
68
+ const normalized = entry.trim();
69
+ if (path.isAbsolute(normalized)) {
70
+ throw new Error(`manifest entry must be a relative path: ${entry}`);
120
71
  }
121
- if (path.isAbsolute(normalizedExec)) {
122
- throw new Error(`manifest module.exec must be a relative path under bin/: ${moduleExec}`);
123
- }
124
- const binDir = path.resolve(pluginDir, "bin");
125
- const targetPath = path.resolve(binDir, normalizedExec);
126
- if (!(0, fs_utils_1.isPathInside)(binDir, targetPath) || targetPath === binDir) {
127
- throw new Error(`manifest module.exec must point to a file under bin/: ${moduleExec}`);
72
+ const targetPath = path.resolve(pluginDir, normalized);
73
+ const pluginDirResolved = path.resolve(pluginDir);
74
+ if (!(0, fs_utils_1.isPathInside)(pluginDirResolved, targetPath) || targetPath === pluginDirResolved) {
75
+ throw new Error(`manifest entry must point to a file under the plugin directory: ${entry}`);
128
76
  }
129
77
  if (!fs.existsSync(targetPath) || !fs.lstatSync(targetPath).isFile()) {
130
- throw new Error(`module entrypoint not found at ${targetPath}`);
78
+ throw new Error(`manifest entry file not found: ${entry}`);
131
79
  }
132
80
  }
133
- function hasPackageJson(pluginDir) {
134
- const packageJsonPath = path.join(pluginDir, "package.json");
135
- return fs.existsSync(packageJsonPath) && fs.lstatSync(packageJsonPath).isFile();
81
+ function copyEntryDirectory(pluginDir, bundleDir, entry) {
82
+ const normalized = entry.trim();
83
+ const entryDir = path.dirname(normalized);
84
+ const sourceDir = path.join(pluginDir, entryDir);
85
+ const destDir = path.join(bundleDir, entryDir);
86
+ (0, fs_utils_1.copyDirectoryRecursiveStrict)(sourceDir, destDir);
136
87
  }
137
- function runCommand(program, args, cwd, verbose) {
138
- if (verbose) {
139
- process.stderr.write(`Running command in ${cwd}: ${program} ${args.join(" ")}\n`);
140
- }
141
- const result = (0, node_child_process_1.spawnSync)(program, args, {
142
- cwd,
143
- stdio: ["ignore", verbose ? "inherit" : "ignore", "inherit"],
144
- });
145
- if (result.error) {
146
- throw new Error(`failed to spawn '${program}' in ${cwd}: ${result.error.message}`);
147
- }
148
- if (result.status === 0) {
149
- return;
150
- }
151
- throw new Error(`command failed (${program} ${args.join(" ")}), exit code ${result.status}`);
152
- }
153
- function ensurePackageLock(pluginDir, verbose) {
154
- if (!hasPackageJson(pluginDir)) {
88
+ function ensurePackageLock(pluginDir, bundleDir, verbose) {
89
+ if (!(0, build_1.hasPackageJson)(pluginDir)) {
155
90
  return;
156
91
  }
157
92
  const lockPath = path.join(pluginDir, "package-lock.json");
158
93
  if (fs.existsSync(lockPath) && fs.lstatSync(lockPath).isFile()) {
159
94
  return;
160
95
  }
96
+ const packageJsonPath = path.join(pluginDir, "package.json");
97
+ (0, fs_utils_1.copyFileStrict)(packageJsonPath, path.join(bundleDir, "package.json"));
161
98
  if (verbose) {
162
- process.stderr.write(`Generating package-lock.json in ${pluginDir}\n`);
99
+ process.stderr.write(`Generating package-lock.json in staging\n`);
163
100
  }
164
- runCommand(npmExecutable(), ["install", "--package-lock-only", "--ignore-scripts", "--no-audit", "--no-fund"], pluginDir, verbose);
101
+ (0, build_1.runCommand)((0, build_1.npmExecutable)(), ["install", "--package-lock-only", "--ignore-scripts", "--no-audit", "--no-fund"], bundleDir, verbose);
165
102
  }
166
103
  function copyNpmFilesToStaging(pluginDir, bundleDir) {
167
104
  const packageJsonPath = path.join(pluginDir, "package.json");
@@ -169,11 +106,14 @@ function copyNpmFilesToStaging(pluginDir, bundleDir) {
169
106
  if (!fs.existsSync(packageJsonPath) || !fs.lstatSync(packageJsonPath).isFile()) {
170
107
  throw new Error(`missing package.json at ${packageJsonPath}`);
171
108
  }
172
- if (!fs.existsSync(lockPath) || !fs.lstatSync(lockPath).isFile()) {
173
- throw new Error(`missing package-lock.json at ${lockPath}`);
174
- }
175
109
  (0, fs_utils_1.copyFileStrict)(packageJsonPath, path.join(bundleDir, "package.json"));
176
- (0, fs_utils_1.copyFileStrict)(lockPath, path.join(bundleDir, "package-lock.json"));
110
+ const stagedLockPath = path.join(bundleDir, "package-lock.json");
111
+ if (fs.existsSync(stagedLockPath) && fs.lstatSync(stagedLockPath).isFile()) {
112
+ return;
113
+ }
114
+ if (fs.existsSync(lockPath) && fs.lstatSync(lockPath).isFile()) {
115
+ (0, fs_utils_1.copyFileStrict)(lockPath, path.join(bundleDir, "package-lock.json"));
116
+ }
177
117
  }
178
118
  function rejectNativeAddonsRecursive(dirPath) {
179
119
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
@@ -197,7 +137,7 @@ function rejectNativeAddonsRecursive(dirPath) {
197
137
  }
198
138
  function installNpmDependencies(pluginDir, bundleDir, verbose) {
199
139
  copyNpmFilesToStaging(pluginDir, bundleDir);
200
- runCommand(npmExecutable(), ["ci", "--omit=dev", "--ignore-scripts", "--no-bin-links", "--no-audit", "--no-fund"], bundleDir, verbose);
140
+ (0, build_1.runCommand)((0, build_1.npmExecutable)(), ["ci", "--omit=dev", "--ignore-scripts", "--no-bin-links", "--no-audit", "--no-fund"], bundleDir, verbose);
201
141
  const nodeModulesPath = path.join(bundleDir, "node_modules");
202
142
  if (!fs.existsSync(nodeModulesPath)) {
203
143
  return;
@@ -208,14 +148,19 @@ function installNpmDependencies(pluginDir, bundleDir, verbose) {
208
148
  rejectNativeAddonsRecursive(nodeModulesPath);
209
149
  }
210
150
  function copyIcon(pluginDir, bundleDir) {
211
- for (const extension of ICON_EXTENSIONS) {
212
- const fileName = `icon.${extension}`;
213
- const sourcePath = path.join(pluginDir, fileName);
214
- if (!fs.existsSync(sourcePath)) {
215
- continue;
151
+ const entries = fs.readdirSync(pluginDir, { withFileTypes: true });
152
+ const iconEntries = entries.filter((e) => {
153
+ if (!e.isFile())
154
+ return false;
155
+ const name = e.name.toLowerCase();
156
+ return name.startsWith("icon.") && ICON_EXTENSIONS.includes(name.slice(5));
157
+ });
158
+ for (const ext of ICON_EXTENSIONS) {
159
+ const found = iconEntries.find((e) => e.name.toLowerCase() === `icon.${ext}`);
160
+ if (found) {
161
+ (0, fs_utils_1.copyFileStrict)(path.join(pluginDir, found.name), path.join(bundleDir, found.name));
162
+ return;
216
163
  }
217
- (0, fs_utils_1.copyFileStrict)(sourcePath, path.join(bundleDir, fileName));
218
- return;
219
164
  }
220
165
  }
221
166
  function uniqueStagingDir(outDir) {
@@ -235,17 +180,22 @@ async function writeTarGz(outPath, baseDir, folderName) {
235
180
  }, [folderName]);
236
181
  }
237
182
  async function bundlePlugin(parsedArgs) {
238
- const { pluginDir, outDir, verbose, noNpmDeps } = parsedArgs;
183
+ const { pluginDir, outDir, verbose, noNpmDeps, noBuild } = parsedArgs;
239
184
  if (verbose) {
240
185
  process.stderr.write(`Bundling plugin from: ${pluginDir}\n`);
241
186
  }
242
- const { pluginId, moduleExec, manifestPath } = readManifest(pluginDir);
187
+ const { pluginId, moduleExec, entry, manifestPath } = noBuild
188
+ ? (0, build_1.readManifest)(pluginDir)
189
+ : (0, build_1.buildPluginAssets)({ pluginDir, verbose });
243
190
  const themesPath = path.join(pluginDir, "themes");
244
191
  const hasThemes = fs.existsSync(themesPath) && fs.lstatSync(themesPath).isDirectory();
245
- if (!moduleExec && !hasThemes) {
246
- throw new Error("manifest has no module.exec or themes/");
192
+ if (entry) {
193
+ validateManifestEntry(pluginDir, entry);
247
194
  }
248
- validateDeclaredModuleExec(pluginDir, moduleExec);
195
+ if (!moduleExec && !hasThemes && !entry) {
196
+ throw new Error("manifest has no module.exec, entry, or themes/");
197
+ }
198
+ (0, build_1.validateDeclaredModuleExec)(pluginDir, moduleExec);
249
199
  (0, fs_utils_1.ensureDirectory)(outDir);
250
200
  const stagingRoot = uniqueStagingDir(outDir);
251
201
  const bundleDir = path.join(stagingRoot, pluginId);
@@ -253,6 +203,9 @@ async function bundlePlugin(parsedArgs) {
253
203
  try {
254
204
  (0, fs_utils_1.copyFileStrict)(manifestPath, path.join(bundleDir, "openvcs.plugin.json"));
255
205
  copyIcon(pluginDir, bundleDir);
206
+ if (entry) {
207
+ copyEntryDirectory(pluginDir, bundleDir, entry);
208
+ }
256
209
  const sourceBinDir = path.join(pluginDir, "bin");
257
210
  if (fs.existsSync(sourceBinDir) && fs.lstatSync(sourceBinDir).isDirectory()) {
258
211
  (0, fs_utils_1.copyDirectoryRecursiveStrict)(sourceBinDir, path.join(bundleDir, "bin"));
@@ -260,8 +213,8 @@ async function bundlePlugin(parsedArgs) {
260
213
  if (hasThemes) {
261
214
  (0, fs_utils_1.copyDirectoryRecursiveStrict)(themesPath, path.join(bundleDir, "themes"));
262
215
  }
263
- if (!noNpmDeps && hasPackageJson(pluginDir)) {
264
- ensurePackageLock(pluginDir, verbose);
216
+ if (!noNpmDeps && (0, build_1.hasPackageJson)(pluginDir)) {
217
+ ensurePackageLock(pluginDir, bundleDir, verbose);
265
218
  installNpmDependencies(pluginDir, bundleDir, verbose);
266
219
  }
267
220
  const outPath = path.join(outDir, `${pluginId}.ovcsp`);
@@ -278,9 +231,10 @@ async function bundlePlugin(parsedArgs) {
278
231
  exports.__private = {
279
232
  ICON_EXTENSIONS,
280
233
  copyIcon,
281
- readManifest,
234
+ readManifest: build_1.readManifest,
282
235
  rejectNativeAddonsRecursive,
283
236
  uniqueStagingDir,
284
- validateDeclaredModuleExec,
237
+ validateDeclaredModuleExec: build_1.validateDeclaredModuleExec,
238
+ validateManifestEntry,
285
239
  writeTarGz,
286
240
  };