plugin-updater 1.0.0 → 1.0.8
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/.github/workflows/publish.yml +29 -0
- package/.idea/misc.xml +7 -0
- package/.idea/modules.xml +8 -0
- package/.idea/plugin-updater.iml +9 -0
- package/.idea/vcs.xml +6 -0
- package/SPEC.md +13 -0
- package/index.js +96 -38
- package/package.json +8 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: '24'
|
|
21
|
+
registry-url: 'https://registry.npmjs.org'
|
|
22
|
+
|
|
23
|
+
- name: Set version from tag
|
|
24
|
+
run: |
|
|
25
|
+
VERSION="${{ github.ref_name }}"
|
|
26
|
+
VERSION="${VERSION#v}"
|
|
27
|
+
npm version $VERSION --allow-same-version --no-git-tag-version
|
|
28
|
+
|
|
29
|
+
- run: npm publish --access public
|
package/.idea/misc.xml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="KubernetesApiProvider"><![CDATA[{}]]></component>
|
|
4
|
+
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="25" project-jdk-type="JavaSDK">
|
|
5
|
+
<output url="file://$PROJECT_DIR$/out" />
|
|
6
|
+
</component>
|
|
7
|
+
</project>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/plugin-updater.iml" filepath="$PROJECT_DIR$/.idea/plugin-updater.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="JAVA_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
4
|
+
<exclude-output />
|
|
5
|
+
<content url="file://$MODULE_DIR$" />
|
|
6
|
+
<orderEntry type="inheritedJdk" />
|
|
7
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
8
|
+
</component>
|
|
9
|
+
</module>
|
package/.idea/vcs.xml
ADDED
package/SPEC.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Plugin Updater - Specifications & Test Requirements
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Reliable core update mechanism for all OpenCode and Claude Code plugins.
|
|
5
|
+
|
|
6
|
+
## Requirements
|
|
7
|
+
- [ ] **Installation Order**: Must be installed FIRST in OpenCode, as it is responsible for installing all other plugins.
|
|
8
|
+
- [ ] **Reliability**: Must never fail or crash, as the entire ecosystem depends on it.
|
|
9
|
+
- [ ] **Launch Detection (Early Launch)**:
|
|
10
|
+
- The updater exports an `earlyLaunch(configDir)` function.
|
|
11
|
+
- Hub plugins (opencode-hub / claude-hub) MUST detect the updater and call `earlyLaunch` before OpenCode invokes it, deferring update flow management to the Hub.
|
|
12
|
+
- If launched directly via the normal application command (no hub / optional dependency), the updater executes its update routine automatically and MUST NOT install the Hub, as the Hub is strictly optional.
|
|
13
|
+
- Path resolution relies on the `configDir` passed by `earlyLaunch` or inferred from `process.argv`/input, NEVER relying on static environment variables like `CC_LAUNCHER`.
|
package/index.js
CHANGED
|
@@ -1,32 +1,52 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
1
|
+
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
3
4
|
const { execSync } = require('child_process');
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
let EARLY_LAUNCH_CONFIG_DIR = null;
|
|
6
7
|
|
|
7
|
-
function
|
|
8
|
+
function getAppConfigDir(appName) {
|
|
9
|
+
if (EARLY_LAUNCH_CONFIG_DIR) {
|
|
10
|
+
return EARLY_LAUNCH_CONFIG_DIR;
|
|
11
|
+
}
|
|
12
|
+
const home = os.homedir();
|
|
13
|
+
const directPath = path.join(home, `.${appName}`);
|
|
14
|
+
const configPath = path.join(home, ".config", appName);
|
|
15
|
+
return fs.existsSync(directPath) ? directPath : configPath;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getReposDir() {
|
|
19
|
+
const isClaude = process.argv.join(' ').includes('claude');
|
|
20
|
+
const appName = isClaude ? "claude" : "opencode";
|
|
21
|
+
return path.join(getAppConfigDir(appName), "repos");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function executeGit(command, cwd) {
|
|
8
25
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
26
|
+
execSync(command, { cwd, stdio: "ignore" });
|
|
27
|
+
return true;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(`[Updater] Git error in ${cwd}: ${error.message}`);
|
|
12
30
|
return false;
|
|
13
31
|
}
|
|
14
32
|
}
|
|
15
33
|
|
|
16
|
-
|
|
34
|
+
const updaterAPI = {
|
|
17
35
|
name: "plugin-updater",
|
|
18
36
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
earlyLaunch: function(configDir) {
|
|
38
|
+
EARLY_LAUNCH_CONFIG_DIR = configDir;
|
|
39
|
+
global.__PLUGIN_UPDATER_HANDLED_BY_HUB__ = true;
|
|
40
|
+
},
|
|
41
|
+
|
|
22
42
|
updatePlugin: function(pluginName, gitUrl, branch = null, commitHash = null) {
|
|
23
|
-
const
|
|
43
|
+
const reposDir = getReposDir();
|
|
44
|
+
const targetDir = path.join(reposDir, pluginName);
|
|
24
45
|
|
|
25
|
-
// 1. Ensure directory exists and clone or pull
|
|
26
46
|
if (!fs.existsSync(targetDir)) {
|
|
27
|
-
if (!fs.existsSync(
|
|
47
|
+
if (!fs.existsSync(reposDir)) fs.mkdirSync(reposDir, { recursive: true });
|
|
28
48
|
const branchFlag = branch ? `--branch ${branch}` : "";
|
|
29
|
-
executeGit(`git clone --recurse-submodules ${branchFlag} ${gitUrl} ${pluginName}`,
|
|
49
|
+
executeGit(`git clone --recurse-submodules ${branchFlag} ${gitUrl} ${pluginName}`, reposDir);
|
|
30
50
|
} else {
|
|
31
51
|
executeGit("git fetch origin", targetDir);
|
|
32
52
|
if (commitHash) {
|
|
@@ -40,53 +60,91 @@ module.exports = {
|
|
|
40
60
|
}
|
|
41
61
|
executeGit("git submodule update --init --recursive", targetDir);
|
|
42
62
|
}
|
|
43
|
-
|
|
44
63
|
return true;
|
|
45
64
|
},
|
|
46
65
|
|
|
47
|
-
/**
|
|
48
|
-
* Called to deploy the compiled output to the execution directory
|
|
49
|
-
*/
|
|
50
66
|
deployToExecutionDir: function(pluginName, executionPath) {
|
|
51
|
-
const sourceDir = path.join(
|
|
67
|
+
const sourceDir = path.join(getReposDir(), pluginName);
|
|
52
68
|
if (!fs.existsSync(sourceDir)) return false;
|
|
53
69
|
|
|
54
|
-
// Build if package.json exists
|
|
55
70
|
if (fs.existsSync(path.join(sourceDir, "package.json"))) {
|
|
56
71
|
try {
|
|
57
72
|
execSync("npm install", { cwd: sourceDir, stdio: "ignore" });
|
|
58
73
|
execSync("npm run build", { cwd: sourceDir, stdio: "ignore" });
|
|
59
|
-
} catch (
|
|
60
|
-
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(`[Updater] Build failed for ${pluginName}: ${error.message}`);
|
|
61
76
|
}
|
|
62
77
|
}
|
|
63
78
|
|
|
64
|
-
// Determine deployment source (prefer dist, fallback to root)
|
|
65
79
|
const distPath = path.join(sourceDir, "dist");
|
|
66
80
|
const deploySource = fs.existsSync(distPath) ? distPath : sourceDir;
|
|
81
|
+
const pluginExecutionPath = path.join(executionPath, pluginName);
|
|
67
82
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
fs.mkdirSync(executionPath, { recursive: true });
|
|
83
|
+
if (!fs.existsSync(pluginExecutionPath)) {
|
|
84
|
+
fs.mkdirSync(pluginExecutionPath, { recursive: true });
|
|
71
85
|
}
|
|
72
86
|
|
|
73
87
|
try {
|
|
74
|
-
|
|
75
|
-
fs.cpSync(deploySource, executionPath, { recursive: true, force: true });
|
|
88
|
+
fs.cpSync(deploySource, pluginExecutionPath, { recursive: true, force: true });
|
|
76
89
|
return true;
|
|
77
|
-
} catch (
|
|
78
|
-
console.error(`[Updater] Deploy failed: ${
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(`[Updater] Deploy failed for ${pluginName}: ${error.message}`);
|
|
79
92
|
return false;
|
|
80
93
|
}
|
|
81
94
|
},
|
|
82
95
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
rebuild: function(pluginObjOrName) {
|
|
97
|
+
const pluginName = typeof pluginObjOrName === 'string' ? pluginObjOrName : pluginObjOrName.name;
|
|
98
|
+
const targetDir = path.join(getReposDir(), pluginName);
|
|
99
|
+
if (fs.existsSync(targetDir)) {
|
|
100
|
+
try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (e) {}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
disable: function(plugin) {
|
|
106
|
+
try {
|
|
107
|
+
const configDir = EARLY_LAUNCH_CONFIG_DIR || path.dirname(getReposDir());
|
|
108
|
+
const pluginExecutionPath = path.join(configDir, "plugin", plugin.name);
|
|
109
|
+
if (fs.existsSync(pluginExecutionPath)) {
|
|
110
|
+
fs.rmSync(pluginExecutionPath, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
uninstall: function(plugin) {
|
|
116
|
+
this.disable(plugin);
|
|
117
|
+
const targetDir = path.join(getReposDir(), plugin.name);
|
|
118
|
+
if (fs.existsSync(targetDir)) {
|
|
119
|
+
try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (e) {}
|
|
120
|
+
}
|
|
91
121
|
}
|
|
92
122
|
};
|
|
123
|
+
|
|
124
|
+
const pluginUpdaterEntry = async function(input) {
|
|
125
|
+
if (!global.__PLUGIN_UPDATER_HANDLED_BY_HUB__) {
|
|
126
|
+
const configDir = (input && input.configDir) ? input.configDir : path.dirname(getReposDir());
|
|
127
|
+
updaterAPI.earlyLaunch(configDir);
|
|
128
|
+
|
|
129
|
+
const pluginsJsonPath = path.join(configDir, "config", "plugins.json");
|
|
130
|
+
if (fs.existsSync(pluginsJsonPath)) {
|
|
131
|
+
try {
|
|
132
|
+
const plugins = JSON.parse(fs.readFileSync(pluginsJsonPath, "utf-8"));
|
|
133
|
+
for (const plugin of plugins) {
|
|
134
|
+
if (plugin.url && plugin.enabled !== false && plugin.type !== "npm") {
|
|
135
|
+
updaterAPI.updatePlugin(plugin.name, plugin.url, plugin.branch || null, plugin.commit || null);
|
|
136
|
+
updaterAPI.deployToExecutionDir(plugin.name, path.join(configDir, "plugin"));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error("[Updater] Failed to parse plugins.json", e);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const apiMethods = { ...updaterAPI };
|
|
148
|
+
delete apiMethods.name;
|
|
149
|
+
Object.assign(pluginUpdaterEntry, apiMethods);
|
|
150
|
+
module.exports = pluginUpdaterEntry;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plugin-updater",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Plugin lifecycle manager for OpenCode and Claude Code launchers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,5 +9,11 @@
|
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/intisy/plugin-updater.git"
|
|
11
11
|
},
|
|
12
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"opencode",
|
|
14
|
+
"claude",
|
|
15
|
+
"plugin",
|
|
16
|
+
"updater",
|
|
17
|
+
"lifecycle"
|
|
18
|
+
]
|
|
13
19
|
}
|