opencode-rules-md 0.8.4 → 0.8.6
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/dist/cli.mjs +906 -0
- package/dist/src/cli/config.d.ts +104 -0
- package/dist/src/cli/config.d.ts.map +1 -0
- package/dist/src/cli/config.js +397 -0
- package/dist/src/cli/config.js.map +1 -0
- package/dist/src/cli/install.d.ts +50 -0
- package/dist/src/cli/install.d.ts.map +1 -0
- package/dist/src/cli/install.js +67 -0
- package/dist/src/cli/install.js.map +1 -0
- package/dist/src/cli/main.d.ts +19 -0
- package/dist/src/cli/main.d.ts.map +1 -0
- package/dist/src/cli/main.js +247 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/cli/real-fs.d.ts +3 -0
- package/dist/src/cli/real-fs.d.ts.map +1 -0
- package/dist/src/cli/real-fs.js +32 -0
- package/dist/src/cli/real-fs.js.map +1 -0
- package/dist/src/cli/registry.d.ts +12 -0
- package/dist/src/cli/registry.d.ts.map +1 -0
- package/dist/src/cli/registry.js +34 -0
- package/dist/src/cli/registry.js.map +1 -0
- package/dist/src/cli/spawn.d.ts +29 -0
- package/dist/src/cli/spawn.d.ts.map +1 -0
- package/dist/src/cli/spawn.js +48 -0
- package/dist/src/cli/spawn.js.map +1 -0
- package/dist/src/cli/status.d.ts +51 -0
- package/dist/src/cli/status.d.ts.map +1 -0
- package/dist/src/cli/status.js +214 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/cli/uninstall.d.ts +30 -0
- package/dist/src/cli/uninstall.d.ts.map +1 -0
- package/dist/src/cli/uninstall.js +128 -0
- package/dist/src/cli/uninstall.js.map +1 -0
- package/dist/src/cli/update.d.ts +65 -0
- package/dist/src/cli/update.d.ts.map +1 -0
- package/dist/src/cli/update.js +205 -0
- package/dist/src/cli/update.js.map +1 -0
- package/package.json +7 -2
- package/src/cli/config.ts +480 -0
- package/src/cli/install.ts +103 -0
- package/src/cli/main.ts +308 -0
- package/src/cli/real-fs.ts +44 -0
- package/src/cli/registry.ts +36 -0
- package/src/cli/spawn.ts +76 -0
- package/src/cli/status.ts +293 -0
- package/src/cli/uninstall.ts +177 -0
- package/src/cli/update.ts +254 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/cli/config.ts
|
|
3
|
+
*
|
|
4
|
+
* Config helpers for the omd CLI installer.
|
|
5
|
+
* Exports: parseJsonc, resolveConfigPath, normalizePlugin, matchesPlugin,
|
|
6
|
+
* dedupePlugins, buildSpecifier, backupIfWritable, rotateBackups,
|
|
7
|
+
* writeAtomically, loadGlobalConfig.
|
|
8
|
+
*
|
|
9
|
+
* Constants:
|
|
10
|
+
* PLUGIN_NAME = "opencode-rules-md"
|
|
11
|
+
* BACKUP_LIMIT = 3
|
|
12
|
+
* OPENCODE_CONFIG_SUBDIR = "opencode"
|
|
13
|
+
*/
|
|
14
|
+
export declare const PLUGIN_NAME: "opencode-rules-md";
|
|
15
|
+
export declare const BACKUP_LIMIT = 3;
|
|
16
|
+
export declare const OPENCODE_CONFIG_SUBDIR = "opencode";
|
|
17
|
+
export interface CliFs {
|
|
18
|
+
readFileSync(path: string): string;
|
|
19
|
+
writeFileSync(path: string, content: string): void;
|
|
20
|
+
renameSync(from: string, to: string): void;
|
|
21
|
+
copyFileSync(from: string, to: string): void;
|
|
22
|
+
unlinkSync(path: string): void;
|
|
23
|
+
mkdirSync(path: string, opts?: {
|
|
24
|
+
recursive?: boolean;
|
|
25
|
+
}): void;
|
|
26
|
+
readdirSync(path: string): string[];
|
|
27
|
+
existsSync(path: string): boolean;
|
|
28
|
+
rmdirSync(path: string): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse JSONC (JSON with Comments) content.
|
|
32
|
+
* Strips single-line and multi-line comments and trailing commas.
|
|
33
|
+
* Returns an empty object for empty/whitespace-only input.
|
|
34
|
+
* Throws if the stripped content is not valid JSON.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseJsonc(content: string): Record<string, unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the config path for a given basename.
|
|
39
|
+
* Prefers .json over .jsonc; returns { path, exists }.
|
|
40
|
+
* The path does NOT need to exist — absent configs are valid for first install.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveConfigPath(fs: CliFs, env: NodeJS.ProcessEnv, basename?: string): {
|
|
43
|
+
path: string;
|
|
44
|
+
exists: boolean;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Normalize a plugin value (undefined, null, string[], or legacy object)
|
|
48
|
+
* to a flat string[].
|
|
49
|
+
*/
|
|
50
|
+
export declare function normalizePlugin(raw: unknown): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Read the installed plugin list from a loaded config, honoring both the
|
|
53
|
+
* modern `plugin` field (singular — what OpenCode actually uses) and the
|
|
54
|
+
* legacy `plugins` field (plural — written by older versions of `omd`).
|
|
55
|
+
*
|
|
56
|
+
* The modern field wins when both are present, matching OpenCode's own
|
|
57
|
+
* resolution order.
|
|
58
|
+
*/
|
|
59
|
+
export declare function readInstalledPlugins(config: GlobalConfig): string[];
|
|
60
|
+
/**
|
|
61
|
+
* Returns true if entry is the plugin named name (with optional @version).
|
|
62
|
+
*/
|
|
63
|
+
export declare function matchesPlugin(entry: string, name?: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Deduplicate plugin list: remove all entries matching PLUGIN_NAME,
|
|
66
|
+
* then re-append the freshest entry (last occurrence). Other plugins preserved.
|
|
67
|
+
*/
|
|
68
|
+
export declare function dedupePlugins(plugins: string[], name?: string): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Build an npm version specifier.
|
|
71
|
+
* '' | undefined -> '@latest'; explicit -> '@<version>'
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildSpecifier(version?: string): string;
|
|
74
|
+
export interface GlobalConfig {
|
|
75
|
+
path: string;
|
|
76
|
+
exists: boolean;
|
|
77
|
+
data: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
/** Backwards-compatible alias used by update.ts / status.ts. */
|
|
80
|
+
export type LoadedConfig = GlobalConfig;
|
|
81
|
+
/**
|
|
82
|
+
* Load and parse a global config file.
|
|
83
|
+
* Returns { path, exists, data }. data is {} when file absent or empty.
|
|
84
|
+
* Throws if the file exists but contains malformed JSON.
|
|
85
|
+
*/
|
|
86
|
+
export declare function loadGlobalConfig(fs: CliFs, env: NodeJS.ProcessEnv, basename?: string): GlobalConfig;
|
|
87
|
+
/**
|
|
88
|
+
* Create a timestamped backup of path if it exists.
|
|
89
|
+
* Returns the backup path, or undefined if no backup was created.
|
|
90
|
+
*/
|
|
91
|
+
export declare function backupIfWritable(fs: CliFs, path: string): string | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Rotate backups: keep at most `limit` backups matching `basename.bak.*`,
|
|
94
|
+
* deleting the oldest (by timestamp sort) when over limit.
|
|
95
|
+
*/
|
|
96
|
+
export declare function rotateBackups(fs: CliFs, dir: string, basename: string, limit?: number): void;
|
|
97
|
+
/**
|
|
98
|
+
* Write content to path atomically:
|
|
99
|
+
* 1. Write to a sibling .tmp.<name> file
|
|
100
|
+
* 2. renameSync over the target
|
|
101
|
+
* 3. Clean up temp file on error
|
|
102
|
+
*/
|
|
103
|
+
export declare function writeAtomically(fs: CliFs, path: string, content: string): void;
|
|
104
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,eAAO,MAAM,WAAW,EAAG,mBAA4B,CAAC;AACxD,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,sBAAsB,aAAa,CAAC;AAMjD,MAAM,WAAW,KAAK;IACpB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACpC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAMD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyFnE;AAgDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,KAAK,EACT,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,QAAQ,GAAE,MAAmB,GAC5B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAanC;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAmBtD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,EAAE,CAMnE;AAUD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAAoB,GAAG,OAAO,CAKhF;AAMD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE,MAAoB,GACzB,MAAM,EAAE,CAiBV;AAMD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAKvD;AAMD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,gEAAgE;AAChE,MAAM,MAAM,YAAY,GAAG,YAAY,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,KAAK,EACT,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,QAAQ,GAAE,MAAmB,GAC5B,YAAY,CAiBX;AAiBJ;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAkB5E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,KAAK,EACT,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAqB,GAC3B,IAAI,CA2BN;AAMD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA+B9E"}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/cli/config.ts
|
|
3
|
+
*
|
|
4
|
+
* Config helpers for the omd CLI installer.
|
|
5
|
+
* Exports: parseJsonc, resolveConfigPath, normalizePlugin, matchesPlugin,
|
|
6
|
+
* dedupePlugins, buildSpecifier, backupIfWritable, rotateBackups,
|
|
7
|
+
* writeAtomically, loadGlobalConfig.
|
|
8
|
+
*
|
|
9
|
+
* Constants:
|
|
10
|
+
* PLUGIN_NAME = "opencode-rules-md"
|
|
11
|
+
* BACKUP_LIMIT = 3
|
|
12
|
+
* OPENCODE_CONFIG_SUBDIR = "opencode"
|
|
13
|
+
*/
|
|
14
|
+
import { join, dirname } from 'path';
|
|
15
|
+
import { homedir } from 'os';
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
// Constants
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
export const PLUGIN_NAME = 'opencode-rules-md';
|
|
20
|
+
export const BACKUP_LIMIT = 3;
|
|
21
|
+
export const OPENCODE_CONFIG_SUBDIR = 'opencode';
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// parseJsonc
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Parse JSONC (JSON with Comments) content.
|
|
27
|
+
* Strips single-line and multi-line comments and trailing commas.
|
|
28
|
+
* Returns an empty object for empty/whitespace-only input.
|
|
29
|
+
* Throws if the stripped content is not valid JSON.
|
|
30
|
+
*/
|
|
31
|
+
export function parseJsonc(content) {
|
|
32
|
+
if (content.trim() === '') {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
// String-aware comment stripping with bracket-depth tracking.
|
|
36
|
+
let out = '';
|
|
37
|
+
let i = 0;
|
|
38
|
+
// Bracket depth outside strings: +1 for {, -1 for }, +1 for [, -1 for ].
|
|
39
|
+
let depth = 0;
|
|
40
|
+
while (i < content.length) {
|
|
41
|
+
const ch = content[i];
|
|
42
|
+
// Start of // comment — strip until newline or end-of-string.
|
|
43
|
+
if (ch === '/' && content[i + 1] === '/' && !isInsideString(out)) {
|
|
44
|
+
let j = i + 2;
|
|
45
|
+
while (j < content.length && content[j] !== '\n') {
|
|
46
|
+
j++;
|
|
47
|
+
}
|
|
48
|
+
out += ' '; // replace the comment with a single space
|
|
49
|
+
// If we stopped at a newline (not EOF): skip the newline — it is the
|
|
50
|
+
// comment terminator, not JSON content. Set i to j so the outer loop
|
|
51
|
+
// increments past the newline without adding it.
|
|
52
|
+
if (j < content.length && content[j] === '\n') {
|
|
53
|
+
i = j; // outer i++ lands on j (the newline), then increments to j+1
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// EOF (j >= content.length): the comment runs to end of file.
|
|
57
|
+
// Preserve a trailing } or ] at EOF as it is likely the JSON closer.
|
|
58
|
+
if (j >= content.length) {
|
|
59
|
+
const last = content[content.length - 1];
|
|
60
|
+
if (last === '}' || last === ']') {
|
|
61
|
+
out += last;
|
|
62
|
+
}
|
|
63
|
+
i = j;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
i = j - 1; // outer i++ will land on j (EOF terminator position)
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
// Start of /* */ comment — strip the whole span
|
|
71
|
+
if (ch === '/' && content[i + 1] === '*' && !isInsideString(out)) {
|
|
72
|
+
let j = i + 2;
|
|
73
|
+
while (j < content.length - 1) {
|
|
74
|
+
if (content[j] === '*' && content[j + 1] === '/') {
|
|
75
|
+
j += 2;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
j++;
|
|
79
|
+
}
|
|
80
|
+
out += ' ';
|
|
81
|
+
i = j;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// Track bracket depth (outside strings)
|
|
85
|
+
if (!isInsideString(out)) {
|
|
86
|
+
if (ch === '{')
|
|
87
|
+
depth++;
|
|
88
|
+
else if (ch === '}')
|
|
89
|
+
depth = Math.max(0, depth - 1);
|
|
90
|
+
else if (ch === '[')
|
|
91
|
+
depth++;
|
|
92
|
+
else if (ch === ']')
|
|
93
|
+
depth = Math.max(0, depth - 1);
|
|
94
|
+
}
|
|
95
|
+
out += ch;
|
|
96
|
+
i++;
|
|
97
|
+
}
|
|
98
|
+
// Phase 2a: strip trailing commas before ] or }
|
|
99
|
+
let s = out.replace(/,(\s*[}\]])/g, '$1');
|
|
100
|
+
// Phase 2b: if at root level (depth > 0 means a } was consumed as comment text
|
|
101
|
+
// at EOF) and s has no closing } or ], add it and strip any trailing comma.
|
|
102
|
+
if (depth > 0 && !/[}\]]/.test(s)) {
|
|
103
|
+
s = s.replace(/,(\s*$)/, '') + '}';
|
|
104
|
+
depth = 0; // we repaired it
|
|
105
|
+
}
|
|
106
|
+
if (s.trim() === '') {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(s);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
const msg = err.message;
|
|
114
|
+
throw new Error('parseJsonc: invalid JSON after stripping comments - ' + msg);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** True if the number of unescaped double or single quotes in `s` is odd. */
|
|
118
|
+
function isInsideString(s) {
|
|
119
|
+
let inStr = false;
|
|
120
|
+
let strChar = '';
|
|
121
|
+
for (let i = 0; i < s.length; i++) {
|
|
122
|
+
const ch = s[i];
|
|
123
|
+
if (!inStr && (ch === '"' || ch === "'")) {
|
|
124
|
+
inStr = true;
|
|
125
|
+
strChar = ch;
|
|
126
|
+
}
|
|
127
|
+
else if (inStr && ch === '\\') {
|
|
128
|
+
i++; // skip escaped char
|
|
129
|
+
}
|
|
130
|
+
else if (inStr && ch === strChar) {
|
|
131
|
+
inStr = false;
|
|
132
|
+
strChar = '';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return inStr;
|
|
136
|
+
}
|
|
137
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
// resolveConfigDir / resolveConfigPath
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
140
|
+
function resolveConfigDir(env) {
|
|
141
|
+
const custom = env.OPENCODE_CONFIG_DIR;
|
|
142
|
+
if (custom && custom.trim() !== '') {
|
|
143
|
+
return custom;
|
|
144
|
+
}
|
|
145
|
+
// Honor the XDG Base Directory Specification when it is set.
|
|
146
|
+
const xdg = env.XDG_CONFIG_HOME;
|
|
147
|
+
if (xdg && xdg.trim() !== '') {
|
|
148
|
+
return join(xdg, OPENCODE_CONFIG_SUBDIR);
|
|
149
|
+
}
|
|
150
|
+
// Fall back to $HOME/.config/opencode before using os.homedir().
|
|
151
|
+
// This keeps tests that control HOME deterministic and avoids loading the
|
|
152
|
+
// wrong global config when the process environment is customized.
|
|
153
|
+
const home = env.HOME;
|
|
154
|
+
if (home && home.trim() !== '') {
|
|
155
|
+
return join(home, '.config', OPENCODE_CONFIG_SUBDIR);
|
|
156
|
+
}
|
|
157
|
+
return join(homedir(), '.config', OPENCODE_CONFIG_SUBDIR);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Resolve the config path for a given basename.
|
|
161
|
+
* Prefers .json over .jsonc; returns { path, exists }.
|
|
162
|
+
* The path does NOT need to exist — absent configs are valid for first install.
|
|
163
|
+
*/
|
|
164
|
+
export function resolveConfigPath(fs, env, basename = 'opencode') {
|
|
165
|
+
const dir = resolveConfigDir(env);
|
|
166
|
+
const jsonPath = join(dir, basename + '.json');
|
|
167
|
+
const jsoncPath = join(dir, basename + '.jsonc');
|
|
168
|
+
if (fs.existsSync(jsonPath)) {
|
|
169
|
+
return { path: jsonPath, exists: true };
|
|
170
|
+
}
|
|
171
|
+
if (fs.existsSync(jsoncPath)) {
|
|
172
|
+
return { path: jsoncPath, exists: true };
|
|
173
|
+
}
|
|
174
|
+
return { path: jsonPath, exists: false };
|
|
175
|
+
}
|
|
176
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
177
|
+
// normalizePlugin
|
|
178
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
179
|
+
/**
|
|
180
|
+
* Normalize a plugin value (undefined, null, string[], or legacy object)
|
|
181
|
+
* to a flat string[].
|
|
182
|
+
*/
|
|
183
|
+
export function normalizePlugin(raw) {
|
|
184
|
+
if (raw == null) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
if (Array.isArray(raw)) {
|
|
188
|
+
return raw.filter((v) => typeof v === 'string');
|
|
189
|
+
}
|
|
190
|
+
if (typeof raw === 'object') {
|
|
191
|
+
const obj = raw;
|
|
192
|
+
return Object.keys(obj).filter(k => obj[k] != null && obj[k] !== false);
|
|
193
|
+
}
|
|
194
|
+
if (typeof raw === 'string') {
|
|
195
|
+
return [raw];
|
|
196
|
+
}
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Read the installed plugin list from a loaded config, honoring both the
|
|
201
|
+
* modern `plugin` field (singular — what OpenCode actually uses) and the
|
|
202
|
+
* legacy `plugins` field (plural — written by older versions of `omd`).
|
|
203
|
+
*
|
|
204
|
+
* The modern field wins when both are present, matching OpenCode's own
|
|
205
|
+
* resolution order.
|
|
206
|
+
*/
|
|
207
|
+
export function readInstalledPlugins(config) {
|
|
208
|
+
const modern = config.data['plugin'];
|
|
209
|
+
if (modern !== undefined) {
|
|
210
|
+
return normalizePlugin(modern);
|
|
211
|
+
}
|
|
212
|
+
return normalizePlugin(config.data['plugins']);
|
|
213
|
+
}
|
|
214
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
215
|
+
// matchesPlugin
|
|
216
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
217
|
+
function escapeRegex(s) {
|
|
218
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Returns true if entry is the plugin named name (with optional @version).
|
|
222
|
+
*/
|
|
223
|
+
export function matchesPlugin(entry, name = PLUGIN_NAME) {
|
|
224
|
+
if (!entry || typeof entry !== 'string')
|
|
225
|
+
return false;
|
|
226
|
+
if (entry === name)
|
|
227
|
+
return true;
|
|
228
|
+
const pattern = new RegExp('^' + escapeRegex(name) + '@');
|
|
229
|
+
return pattern.test(entry);
|
|
230
|
+
}
|
|
231
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
232
|
+
// dedupePlugins
|
|
233
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
234
|
+
/**
|
|
235
|
+
* Deduplicate plugin list: remove all entries matching PLUGIN_NAME,
|
|
236
|
+
* then re-append the freshest entry (last occurrence). Other plugins preserved.
|
|
237
|
+
*/
|
|
238
|
+
export function dedupePlugins(plugins, name = PLUGIN_NAME) {
|
|
239
|
+
const others = [];
|
|
240
|
+
let lastFresh;
|
|
241
|
+
for (const p of plugins) {
|
|
242
|
+
if (matchesPlugin(p, name)) {
|
|
243
|
+
lastFresh = p;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
others.push(p);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const result = [...others];
|
|
250
|
+
if (lastFresh !== undefined) {
|
|
251
|
+
result.push(lastFresh);
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
256
|
+
// buildSpecifier
|
|
257
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
258
|
+
/**
|
|
259
|
+
* Build an npm version specifier.
|
|
260
|
+
* '' | undefined -> '@latest'; explicit -> '@<version>'
|
|
261
|
+
*/
|
|
262
|
+
export function buildSpecifier(version) {
|
|
263
|
+
if (!version || version.trim() === '') {
|
|
264
|
+
return '@latest';
|
|
265
|
+
}
|
|
266
|
+
return '@' + version;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Load and parse a global config file.
|
|
270
|
+
* Returns { path, exists, data }. data is {} when file absent or empty.
|
|
271
|
+
* Throws if the file exists but contains malformed JSON.
|
|
272
|
+
*/
|
|
273
|
+
export function loadGlobalConfig(fs, env, basename = 'opencode') {
|
|
274
|
+
const resolved = resolveConfigPath(fs, env, basename);
|
|
275
|
+
if (!resolved.exists) {
|
|
276
|
+
return { path: resolved.path, exists: false, data: {} };
|
|
277
|
+
}
|
|
278
|
+
const raw = fs.readFileSync(resolved.path);
|
|
279
|
+
try {
|
|
280
|
+
const data = parseJsonc(raw);
|
|
281
|
+
return { path: resolved.path, exists: true, data };
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
throw new Error(`config file at ${resolved.path} is malformed JSON\n` +
|
|
285
|
+
`Fix the JSON error, or delete the file and re-run.\n` +
|
|
286
|
+
` error: ${err.message}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
290
|
+
// Backup helpers
|
|
291
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
292
|
+
function timestamp() {
|
|
293
|
+
const now = new Date();
|
|
294
|
+
const y = now.getUTCFullYear();
|
|
295
|
+
const m = String(now.getUTCMonth() + 1).padStart(2, '0');
|
|
296
|
+
const d = String(now.getUTCDate()).padStart(2, '0');
|
|
297
|
+
const hh = String(now.getUTCHours()).padStart(2, '0');
|
|
298
|
+
const mm = String(now.getUTCMinutes()).padStart(2, '0');
|
|
299
|
+
const ss = String(now.getUTCSeconds()).padStart(2, '0');
|
|
300
|
+
return '' + y + m + d + 'T' + hh + mm + ss;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Create a timestamped backup of path if it exists.
|
|
304
|
+
* Returns the backup path, or undefined if no backup was created.
|
|
305
|
+
*/
|
|
306
|
+
export function backupIfWritable(fs, path) {
|
|
307
|
+
if (!fs.existsSync(path)) {
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const content = fs.readFileSync(path);
|
|
312
|
+
const dir = dirname(path);
|
|
313
|
+
const segs = path.replace(/\\/g, '/').split('/');
|
|
314
|
+
const base = segs[segs.length - 1] ?? path;
|
|
315
|
+
const dot = base.lastIndexOf('.');
|
|
316
|
+
const name = dot >= 0 ? base.slice(0, dot) : base;
|
|
317
|
+
const backupPath = join(dir, name + '.bak.' + timestamp());
|
|
318
|
+
fs.writeFileSync(backupPath, content);
|
|
319
|
+
return backupPath;
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Rotate backups: keep at most `limit` backups matching `basename.bak.*`,
|
|
327
|
+
* deleting the oldest (by timestamp sort) when over limit.
|
|
328
|
+
*/
|
|
329
|
+
export function rotateBackups(fs, dir, basename, limit = BACKUP_LIMIT) {
|
|
330
|
+
let entries = [];
|
|
331
|
+
try {
|
|
332
|
+
entries = fs.readdirSync(dir);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const prefix = basename + '.bak.';
|
|
338
|
+
const backups = entries
|
|
339
|
+
.filter(e => e.startsWith(prefix))
|
|
340
|
+
.map(e => ({ name: e, path: join(dir, e) }))
|
|
341
|
+
.filter(({ path }) => fs.existsSync(path))
|
|
342
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
343
|
+
if (backups.length <= limit) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const toDelete = backups.slice(0, backups.length - limit);
|
|
347
|
+
for (const { path } of toDelete) {
|
|
348
|
+
try {
|
|
349
|
+
fs.unlinkSync(path);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// best-effort
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
357
|
+
// writeAtomically
|
|
358
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
359
|
+
/**
|
|
360
|
+
* Write content to path atomically:
|
|
361
|
+
* 1. Write to a sibling .tmp.<name> file
|
|
362
|
+
* 2. renameSync over the target
|
|
363
|
+
* 3. Clean up temp file on error
|
|
364
|
+
*/
|
|
365
|
+
export function writeAtomically(fs, path, content) {
|
|
366
|
+
const dir = dirname(path);
|
|
367
|
+
try {
|
|
368
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// may already exist
|
|
372
|
+
}
|
|
373
|
+
const segs = path.replace(/\\/g, '/').split('/');
|
|
374
|
+
const base = segs[segs.length - 1] ?? path;
|
|
375
|
+
const dot = base.lastIndexOf('.');
|
|
376
|
+
const name = dot >= 0 ? base.slice(0, dot) : base;
|
|
377
|
+
const ext = dot >= 0 ? base.slice(dot) : '';
|
|
378
|
+
const tmpPath = join(dir, '.tmp.' + name + ext);
|
|
379
|
+
let tempCreated = false;
|
|
380
|
+
try {
|
|
381
|
+
fs.writeFileSync(tmpPath, content);
|
|
382
|
+
tempCreated = true;
|
|
383
|
+
fs.renameSync(tmpPath, path);
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
if (tempCreated) {
|
|
387
|
+
try {
|
|
388
|
+
fs.unlinkSync(tmpPath);
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// ignore cleanup failure
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
throw err;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/cli/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { spawnOpencodePlugin } from './spawn.js';
|
|
2
|
+
import type { CliFs } from './config.js';
|
|
3
|
+
/** Default base specifier used when the caller does not pin a version. */
|
|
4
|
+
export declare const DEFAULT_SPECIFIER: "opencode-rules-md";
|
|
5
|
+
export interface InstallOptions {
|
|
6
|
+
/** Pin to a specific version, e.g. `"2.0.0"`. Falsy means "use the bare specifier". */
|
|
7
|
+
version?: string;
|
|
8
|
+
/** Run the full pipeline without spawning the child process. */
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
/** Reserved for future prompts — currently a no-op. */
|
|
11
|
+
yes?: boolean;
|
|
12
|
+
/** Test hook for resolving the latest version (kept for API compatibility). */
|
|
13
|
+
latestVersion?: string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Test seam: replaces spawnOpencodePlugin. Defaults to the real CLI wrapper.
|
|
16
|
+
* Pass an object with a compatible signature to assert on calls.
|
|
17
|
+
*/
|
|
18
|
+
spawn?: typeof spawnOpencodePlugin;
|
|
19
|
+
}
|
|
20
|
+
export interface InstallResult {
|
|
21
|
+
/** Whether we actually invoked the opencode CLI. */
|
|
22
|
+
status: 'wrote' | 'skipped';
|
|
23
|
+
/** The specifier passed to `opencode plugin`. Useful for logging. */
|
|
24
|
+
specifier: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build the specifier to pass to `opencode plugin`.
|
|
28
|
+
*
|
|
29
|
+
* Rules:
|
|
30
|
+
* - Empty / unset version → bare `opencode-rules-md` (lets OpenCode refresh).
|
|
31
|
+
* - Any other value → `opencode-rules-md@<version>` (pins the install).
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildSpecifier(version: string | undefined): string;
|
|
34
|
+
/**
|
|
35
|
+
* Install opencode-rules-md via OpenCode's own plugin command.
|
|
36
|
+
*
|
|
37
|
+
* Options:
|
|
38
|
+
* version — optional npm version pin
|
|
39
|
+
* dryRun — print the would-be command and skip the spawn
|
|
40
|
+
* yes — reserved
|
|
41
|
+
*
|
|
42
|
+
* Returns:
|
|
43
|
+
* { status: 'skipped', specifier } when dryRun is true
|
|
44
|
+
* { status: 'wrote', specifier } on a clean exit
|
|
45
|
+
*
|
|
46
|
+
* Throws on non-zero exit, so the caller (main.ts) can map it to a CLI
|
|
47
|
+
* failure without us swallowing the error.
|
|
48
|
+
*/
|
|
49
|
+
export declare const runInstall: (opts?: InstallOptions, _fs?: CliFs, env?: NodeJS.ProcessEnv) => Promise<InstallResult>;
|
|
50
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/cli/install.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGzC,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,qBAAc,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,+EAA+E;IAC/E,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,mBAAmB,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAMlE;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,GACrB,OAAM,cAAmB,EAIzB,MAAM,KAAK,EACX,MAAM,MAAM,CAAC,UAAU,KACtB,OAAO,CAAC,aAAa,CAmBvB,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// src/cli/install.ts — `omd install` command implementation.
|
|
3
|
+
//
|
|
4
|
+
// `omd install` is a thin convenience wrapper around OpenCode's own
|
|
5
|
+
// `opencode plugin <specifier> --global` command. OpenCode handles the
|
|
6
|
+
// correct config field name (`plugin`, singular), manifest reading, and
|
|
7
|
+
// cache placement under ~/.cache/opencode/packages/. We no longer edit
|
|
8
|
+
// opencode.json or tui.json directly — duplicating that logic was the
|
|
9
|
+
// source of the original bug (we wrote `plugins` plural, which OpenCode
|
|
10
|
+
// silently ignored).
|
|
11
|
+
//
|
|
12
|
+
// The bare specifier `opencode-rules-md` (no `@latest`) is intentional:
|
|
13
|
+
// it lets OpenCode resolve and refresh the package on every invocation
|
|
14
|
+
// without us pinning to a stale version literal.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
import { spawnOpencodePlugin } from './spawn.js';
|
|
17
|
+
import { PLUGIN_NAME } from './config.js';
|
|
18
|
+
/** Default base specifier used when the caller does not pin a version. */
|
|
19
|
+
export const DEFAULT_SPECIFIER = PLUGIN_NAME;
|
|
20
|
+
/**
|
|
21
|
+
* Build the specifier to pass to `opencode plugin`.
|
|
22
|
+
*
|
|
23
|
+
* Rules:
|
|
24
|
+
* - Empty / unset version → bare `opencode-rules-md` (lets OpenCode refresh).
|
|
25
|
+
* - Any other value → `opencode-rules-md@<version>` (pins the install).
|
|
26
|
+
*/
|
|
27
|
+
export function buildSpecifier(version) {
|
|
28
|
+
const trimmed = version?.trim() ?? '';
|
|
29
|
+
if (!trimmed || trimmed === 'latest') {
|
|
30
|
+
return DEFAULT_SPECIFIER;
|
|
31
|
+
}
|
|
32
|
+
return `${PLUGIN_NAME}@${trimmed}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Install opencode-rules-md via OpenCode's own plugin command.
|
|
36
|
+
*
|
|
37
|
+
* Options:
|
|
38
|
+
* version — optional npm version pin
|
|
39
|
+
* dryRun — print the would-be command and skip the spawn
|
|
40
|
+
* yes — reserved
|
|
41
|
+
*
|
|
42
|
+
* Returns:
|
|
43
|
+
* { status: 'skipped', specifier } when dryRun is true
|
|
44
|
+
* { status: 'wrote', specifier } on a clean exit
|
|
45
|
+
*
|
|
46
|
+
* Throws on non-zero exit, so the caller (main.ts) can map it to a CLI
|
|
47
|
+
* failure without us swallowing the error.
|
|
48
|
+
*/
|
|
49
|
+
export const runInstall = async (opts = {},
|
|
50
|
+
// The next two parameters are kept for API compatibility with main.ts.
|
|
51
|
+
// `omd install` no longer reads or writes the user's config files directly,
|
|
52
|
+
// so fs/env are no longer consulted here.
|
|
53
|
+
_fs, env) => {
|
|
54
|
+
const specifier = buildSpecifier(opts.version);
|
|
55
|
+
const spawnFn = opts.spawn ?? spawnOpencodePlugin;
|
|
56
|
+
const targetEnv = env ?? process.env;
|
|
57
|
+
if (opts.dryRun) {
|
|
58
|
+
console.log(`omd: would run: opencode plugin ${specifier} --global`);
|
|
59
|
+
return { status: 'skipped', specifier };
|
|
60
|
+
}
|
|
61
|
+
const result = await spawnFn([specifier, '--global'], { env: targetEnv, stdio: 'inherit' });
|
|
62
|
+
if ((result.status ?? 0) !== 0) {
|
|
63
|
+
throw new Error(`opencode plugin ${specifier} --global exited with status ${String(result.status)}`);
|
|
64
|
+
}
|
|
65
|
+
return { status: 'wrote', specifier };
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/cli/install.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6DAA6D;AAC7D,EAAE;AACF,oEAAoE;AACpE,uEAAuE;AACvE,wEAAwE;AACxE,uEAAuE;AACvE,sEAAsE;AACtE,wEAAwE;AACxE,qBAAqB;AACrB,EAAE;AACF,wEAAwE;AACxE,uEAAuE;AACvE,iDAAiD;AACjD,8EAA8E;AAE9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAyB7C;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAA2B;IACxD,MAAM,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACtC,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACrC,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IACD,OAAO,GAAG,WAAW,IAAI,OAAO,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAC7B,OAAuB,EAAE;AACzB,uEAAuE;AACvE,4EAA4E;AAC5E,0CAA0C;AAC1C,GAAW,EACX,GAAuB,EACC,EAAE;IAC1B,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,mBAAmB,CAAC;IAClD,MAAM,SAAS,GAAG,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IAErC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,mCAAmC,SAAS,WAAW,CAAC,CAAC;QACrE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IAC1C,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAE5F,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,mBAAmB,SAAS,gCAAgC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CACpF,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AACxC,CAAC,CAAC"}
|