opencode-rules-md 0.8.4 → 0.8.5
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 +915 -0
- package/dist/src/cli/config.d.ts +93 -0
- package/dist/src/cli/config.d.ts.map +1 -0
- package/dist/src/cli/config.js +382 -0
- package/dist/src/cli/config.js.map +1 -0
- package/dist/src/cli/install.d.ts +35 -0
- package/dist/src/cli/install.d.ts.map +1 -0
- package/dist/src/cli/install.js +148 -0
- package/dist/src/cli/install.js.map +1 -0
- package/dist/src/cli/main.d.ts +14 -0
- package/dist/src/cli/main.d.ts.map +1 -0
- package/dist/src/cli/main.js +244 -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/status.d.ts +44 -0
- package/dist/src/cli/status.d.ts.map +1 -0
- package/dist/src/cli/status.js +182 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/cli/uninstall.d.ts +27 -0
- package/dist/src/cli/uninstall.d.ts.map +1 -0
- package/dist/src/cli/uninstall.js +101 -0
- package/dist/src/cli/uninstall.js.map +1 -0
- package/dist/src/cli/update.d.ts +31 -0
- package/dist/src/cli/update.d.ts.map +1 -0
- package/dist/src/cli/update.js +173 -0
- package/dist/src/cli/update.js.map +1 -0
- package/package.json +7 -2
- package/src/cli/config.ts +461 -0
- package/src/cli/install.ts +211 -0
- package/src/cli/main.ts +299 -0
- package/src/cli/real-fs.ts +44 -0
- package/src/cli/registry.ts +36 -0
- package/src/cli/status.ts +247 -0
- package/src/cli/uninstall.ts +146 -0
- package/src/cli/update.ts +220 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// src/cli/install.ts — `omd install` command implementation.
|
|
3
|
+
//
|
|
4
|
+
// Dual-config install loop: iterates over ["opencode", "tui"], loads each
|
|
5
|
+
// config, normalizes + deduplicates the plugin list, appends the fresh
|
|
6
|
+
// specifier, and writes atomically with a timestamped backup. When the version
|
|
7
|
+
// is omitted or "latest", the latest published npm version is resolved so the
|
|
8
|
+
// written specifier matches the concrete version (important for accurate
|
|
9
|
+
// update checks) and any stale local cache can be purged.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
import { PLUGIN_NAME, loadGlobalConfig, matchesPlugin, normalizePlugin, backupIfWritable, rotateBackups, writeAtomically, } from './config.js';
|
|
13
|
+
import { fetchLatestVersion } from './registry.js';
|
|
14
|
+
import { purgeDirectory, resolveCachePath } from './update.js';
|
|
15
|
+
export const CONFIG_BASENAMES = ['opencode', 'tui'];
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the concrete version to write.
|
|
18
|
+
*
|
|
19
|
+
* - If the user supplied an explicit version (not "latest" and not empty), use
|
|
20
|
+
* it unchanged.
|
|
21
|
+
* - Otherwise, ask the npm registry for the latest published version.
|
|
22
|
+
* - If the registry is unreachable, fall back to the literal "@latest" tag so
|
|
23
|
+
* the install command never hard-fails due to network issues. This preserves
|
|
24
|
+
* the previous behavior while still allowing the cache check to compare when
|
|
25
|
+
* a concrete version is known.
|
|
26
|
+
*/
|
|
27
|
+
async function resolveVersion(opts) {
|
|
28
|
+
const raw = opts.version?.trim() ?? '';
|
|
29
|
+
const wantsLatest = raw === '' || raw === 'latest';
|
|
30
|
+
if (!wantsLatest) {
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
// Tests can short-circuit the network call via latestVersion.
|
|
34
|
+
if (opts.latestVersion !== undefined) {
|
|
35
|
+
return opts.latestVersion;
|
|
36
|
+
}
|
|
37
|
+
const latest = await fetchLatestVersion();
|
|
38
|
+
return latest ?? 'latest';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read the version field from a cached package.json, if present.
|
|
42
|
+
* Returns null when the file is missing or unreadable.
|
|
43
|
+
*/
|
|
44
|
+
function readCacheVersion(fs, cachePath) {
|
|
45
|
+
const pkgPath = join(cachePath, 'package.json');
|
|
46
|
+
try {
|
|
47
|
+
if (!fs.existsSync(pkgPath)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath));
|
|
51
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// A broken cache is treated as "version unknown" so it gets purged.
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Purge the local cache directory when it is stale relative to the version we
|
|
60
|
+
* just installed. If the cache version cannot be determined, we err on the
|
|
61
|
+
* side of purging so the next npx invocation fetches a fresh copy.
|
|
62
|
+
*/
|
|
63
|
+
function purgeCacheIfStale(fs, env, resolvedVersion) {
|
|
64
|
+
// The literal "latest" tag is not a comparable version; we only purge when
|
|
65
|
+
// we have a concrete resolved version to compare against.
|
|
66
|
+
if (resolvedVersion === 'latest') {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const cachePath = resolveCachePath(env);
|
|
70
|
+
if (!fs.existsSync(cachePath)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const cacheVersion = readCacheVersion(fs, cachePath);
|
|
74
|
+
if (cacheVersion === null || cacheVersion !== resolvedVersion) {
|
|
75
|
+
purgeDirectory(fs, cachePath);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Install opencode-rules-md into both opencode.json and tui.json configs.
|
|
82
|
+
*
|
|
83
|
+
* Options:
|
|
84
|
+
* version — npm version specifier (default: latest from registry)
|
|
85
|
+
* dryRun — run full pipeline without writing to disk
|
|
86
|
+
* yes — accepted for future prompts, no-op here
|
|
87
|
+
* latestVersion — test hook for the resolved latest version
|
|
88
|
+
*/
|
|
89
|
+
export const runInstall = async (opts = {}, fs, env) => {
|
|
90
|
+
const resolvedVersion = await resolveVersion(opts);
|
|
91
|
+
const freshEntry = `${PLUGIN_NAME}@${resolvedVersion}`;
|
|
92
|
+
const results = [];
|
|
93
|
+
let anyProcessed = false;
|
|
94
|
+
for (const basename of CONFIG_BASENAMES) {
|
|
95
|
+
const loaded = loadGlobalConfig(fs, env, basename);
|
|
96
|
+
const plugins = normalizePlugin(loaded.data['plugins']);
|
|
97
|
+
// Find existing entry for this plugin
|
|
98
|
+
const existingEntry = plugins.find(p => matchesPlugin(p));
|
|
99
|
+
// No-op if the same specifier is already installed
|
|
100
|
+
if (existingEntry === freshEntry) {
|
|
101
|
+
results.push({ path: loaded.path, status: 'skipped', backup: null });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
anyProcessed = true;
|
|
105
|
+
// Build the new plugin list: remove all matching entries, append fresh specifier.
|
|
106
|
+
// Existing non-matching plugins are preserved in their original order.
|
|
107
|
+
const withoutStale = plugins.filter(p => !matchesPlugin(p));
|
|
108
|
+
const newPlugins = [...withoutStale, freshEntry];
|
|
109
|
+
const newData = { ...loaded.data, plugins: newPlugins };
|
|
110
|
+
const newContent = JSON.stringify(newData, null, 2) + '\n';
|
|
111
|
+
if (opts.dryRun) {
|
|
112
|
+
results.push({ path: loaded.path, status: 'wrote', backup: null });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Backup the existing file if it exists
|
|
116
|
+
let backup;
|
|
117
|
+
if (loaded.exists) {
|
|
118
|
+
backup = backupIfWritable(fs, loaded.path);
|
|
119
|
+
if (backup !== undefined) {
|
|
120
|
+
const dir = dirname(loaded.path);
|
|
121
|
+
const segs = loaded.path.replace(/\\/g, '/').split('/');
|
|
122
|
+
const base = segs[segs.length - 1] ?? loaded.path;
|
|
123
|
+
const dot = base.lastIndexOf('.');
|
|
124
|
+
const name = dot >= 0 ? base.slice(0, dot) : base;
|
|
125
|
+
rotateBackups(fs, dir, name, 3);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
writeAtomically(fs, loaded.path, newContent);
|
|
129
|
+
results.push({
|
|
130
|
+
path: loaded.path,
|
|
131
|
+
status: 'wrote',
|
|
132
|
+
backup: backup ?? null,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// After writing the configs, purge a stale local cache so the next run of the
|
|
136
|
+
// plugin starts from the version we just registered. In dry-run mode we
|
|
137
|
+
// never touch the filesystem.
|
|
138
|
+
let purged = false;
|
|
139
|
+
if (!opts.dryRun) {
|
|
140
|
+
purged = purgeCacheIfStale(fs, env, resolvedVersion);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
status: anyProcessed ? 'wrote' : 'skipped',
|
|
144
|
+
results,
|
|
145
|
+
purged,
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/cli/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CliFs } from './config.js';
|
|
2
|
+
export interface MainOptions {
|
|
3
|
+
fs?: CliFs;
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
stdout?: (s: string) => void;
|
|
6
|
+
stderr?: (s: string) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Optional injection point for tests: pretend the npm registry returned this
|
|
9
|
+
* version when "latest" is requested by install or update.
|
|
10
|
+
*/
|
|
11
|
+
latestVersion?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const runMain: (opts: MainOptions, argv: string[]) => Promise<number>;
|
|
14
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/cli/main.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAuHzC,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,KAAK,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,OAAO,GAClB,MAAM,WAAW,EACjB,MAAM,MAAM,EAAE,KACb,OAAO,CAAC,MAAM,CA4IhB,CAAC"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// src/cli/main.ts — `omd` CLI entry point.
|
|
3
|
+
//
|
|
4
|
+
// Command dispatch using node:util.parseArgs.
|
|
5
|
+
// Bare `omd` → install (default).
|
|
6
|
+
// Exit codes: 0 success/no-op, 1 health failure, 2 usage error.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
import { parseArgs } from 'node:util';
|
|
9
|
+
import { runInstall } from './install.js';
|
|
10
|
+
import { runUninstall } from './uninstall.js';
|
|
11
|
+
import { runStatus, runDoctor } from './status.js';
|
|
12
|
+
import { runUpdate } from './update.js';
|
|
13
|
+
import { createRealFs } from './real-fs.js';
|
|
14
|
+
// ─── Usage ───────────────────────────────────────────────────────────────────
|
|
15
|
+
const USAGE = `omd — opencode-rules-md plugin manager
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
omd [command] [options]
|
|
19
|
+
|
|
20
|
+
Commands:
|
|
21
|
+
install Register opencode-rules-md in both opencode.json and tui.json
|
|
22
|
+
uninstall Remove opencode-rules-md from both configs
|
|
23
|
+
status Show installed plugin state for each config
|
|
24
|
+
doctor Run health checks for the plugin environment
|
|
25
|
+
update Check for new versions and purge stale cache
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--dry-run Show what would be changed without writing
|
|
29
|
+
--version Pin to a specific version (install only)
|
|
30
|
+
--latest Use the latest version (install only)
|
|
31
|
+
--purge Also remove ~/.cache/opencode/node_modules/opencode-rules-md (uninstall only)
|
|
32
|
+
--yes Accept all prompts automatically
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
omd # install with defaults (latest)
|
|
36
|
+
omd install # same as bare omd
|
|
37
|
+
omd install --dry-run
|
|
38
|
+
omd install --version 2.0.0
|
|
39
|
+
omd uninstall --purge
|
|
40
|
+
`.trim();
|
|
41
|
+
const printUsage = (stdout) => {
|
|
42
|
+
stdout(USAGE);
|
|
43
|
+
};
|
|
44
|
+
// ─── Manual argv parsing — extract command before passing to parseArgs ────────
|
|
45
|
+
/**
|
|
46
|
+
* Known option flags (long-form).
|
|
47
|
+
* We detect unknown flags by scanning argv for anything starting with `-` or `--`
|
|
48
|
+
* that is NOT in this set.
|
|
49
|
+
*/
|
|
50
|
+
const KNOWN_FLAGS = new Set([
|
|
51
|
+
'--help', '--dry-run', '--latest', '--purge', '--yes', '--version',
|
|
52
|
+
'-h', '-V',
|
|
53
|
+
]);
|
|
54
|
+
/**
|
|
55
|
+
* Known short options that take a value.
|
|
56
|
+
* e.g. `--version 2.0.0` → values.version = '2.0.0'
|
|
57
|
+
*/
|
|
58
|
+
const SHORT_OPTIONS_WITH_VALUE = new Set(['v']);
|
|
59
|
+
/**
|
|
60
|
+
* Known long options that take a value.
|
|
61
|
+
*/
|
|
62
|
+
const LONG_OPTIONS_WITH_VALUE = new Set(['version']);
|
|
63
|
+
/**
|
|
64
|
+
* Extract the command name (first non-option positional) from argv.
|
|
65
|
+
* Also returns unknown flags found before the command.
|
|
66
|
+
*
|
|
67
|
+
* Examples:
|
|
68
|
+
* [] → { command: 'install', args: [], unknownFlags: [] }
|
|
69
|
+
* ['install'] → { command: 'install', args: [], unknownFlags: [] }
|
|
70
|
+
* ['notacommand'] → { command: 'notacommand', args: [], unknownFlags: [] }
|
|
71
|
+
* ['install', '--dry-run'] → { command: 'install', args: ['--dry-run'], unknownFlags: [] }
|
|
72
|
+
* ['install', '--unknown-opt'] → { command: 'install', args: ['--unknown-opt'], unknownFlags: ['--unknown-opt'] }
|
|
73
|
+
*/
|
|
74
|
+
function extractCommand(argv) {
|
|
75
|
+
const unknownFlags = [];
|
|
76
|
+
let command = null;
|
|
77
|
+
for (let i = 0; i < argv.length; i++) {
|
|
78
|
+
const arg = argv[i];
|
|
79
|
+
if (arg === '--') {
|
|
80
|
+
// Argument separator — remaining args are positional
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
if (!arg.startsWith('-')) {
|
|
84
|
+
// First non-option is the command
|
|
85
|
+
if (command === null) {
|
|
86
|
+
command = arg;
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// It's an option — check if known
|
|
91
|
+
if (!KNOWN_FLAGS.has(arg)) {
|
|
92
|
+
unknownFlags.push(arg);
|
|
93
|
+
}
|
|
94
|
+
// Skip the value for options that take one
|
|
95
|
+
const flagName = arg.startsWith('--')
|
|
96
|
+
? arg.slice(2)
|
|
97
|
+
: arg.startsWith('-')
|
|
98
|
+
? arg.slice(1)
|
|
99
|
+
: '';
|
|
100
|
+
if (LONG_OPTIONS_WITH_VALUE.has(flagName) ||
|
|
101
|
+
SHORT_OPTIONS_WITH_VALUE.has(flagName)) {
|
|
102
|
+
// Skip the next argv element if it's not an option itself
|
|
103
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
|
|
104
|
+
i++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { command, unknownFlags };
|
|
109
|
+
}
|
|
110
|
+
export const runMain = async (opts, argv) => {
|
|
111
|
+
const { fs = createRealFs(), env = process.env, stdout = (s) => console.log(s), stderr = (s) => console.error(s), } = opts;
|
|
112
|
+
// Intercept --help/-h before anything else
|
|
113
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
114
|
+
printUsage(stdout);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
// Extract command and detect unknown flags before parseArgs
|
|
118
|
+
const { command, unknownFlags } = extractCommand(argv);
|
|
119
|
+
if (unknownFlags.length > 0) {
|
|
120
|
+
stderr(`omd: unknown option ${unknownFlags[0]}`);
|
|
121
|
+
stderr("Run 'omd --help' for usage.");
|
|
122
|
+
return 2;
|
|
123
|
+
}
|
|
124
|
+
// Default to install for bare `omd`
|
|
125
|
+
const resolvedCommand = command ?? 'install';
|
|
126
|
+
try {
|
|
127
|
+
switch (resolvedCommand) {
|
|
128
|
+
case 'install': {
|
|
129
|
+
// Remaining argv for parseArgs to extract options
|
|
130
|
+
const remaining = argv.slice(argv.indexOf('install') + 1 || argv.indexOf(resolvedCommand) + 1);
|
|
131
|
+
const { values } = parseArgs({
|
|
132
|
+
argv: remaining,
|
|
133
|
+
allowPositionals: true,
|
|
134
|
+
strict: false,
|
|
135
|
+
options: {
|
|
136
|
+
'dry-run': { type: 'boolean', default: false },
|
|
137
|
+
version: { type: 'string', default: undefined },
|
|
138
|
+
latest: { type: 'boolean', default: false },
|
|
139
|
+
yes: { type: 'boolean', default: false },
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
const installOpts = {
|
|
143
|
+
version: values['latest'] ? 'latest' : String(values['version'] ?? ''),
|
|
144
|
+
dryRun: Boolean(values['dry-run']),
|
|
145
|
+
yes: Boolean(values['yes']),
|
|
146
|
+
latestVersion: opts.latestVersion,
|
|
147
|
+
};
|
|
148
|
+
const result = await runInstall(installOpts, fs, env);
|
|
149
|
+
if (result.status === 'skipped') {
|
|
150
|
+
stdout('omd: already installed (no changes needed)');
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
for (const r of result.results) {
|
|
154
|
+
if (r.status === 'wrote') {
|
|
155
|
+
stdout(`omd: registered in ${r.path}`);
|
|
156
|
+
}
|
|
157
|
+
else if (r.status === 'skipped') {
|
|
158
|
+
stdout(`omd: ${r.path} — already up to date`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (result.purged) {
|
|
162
|
+
stdout('omd: cache purged');
|
|
163
|
+
}
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
case 'uninstall': {
|
|
167
|
+
const remaining = argv.slice(argv.indexOf(resolvedCommand) + 1);
|
|
168
|
+
const { values } = parseArgs({
|
|
169
|
+
argv: remaining,
|
|
170
|
+
allowPositionals: true,
|
|
171
|
+
strict: false,
|
|
172
|
+
options: {
|
|
173
|
+
'dry-run': { type: 'boolean', default: false },
|
|
174
|
+
purge: { type: 'boolean', default: false },
|
|
175
|
+
yes: { type: 'boolean', default: false },
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
const uninstallOpts = {
|
|
179
|
+
purge: Boolean(values['purge']),
|
|
180
|
+
dryRun: Boolean(values['dry-run']),
|
|
181
|
+
yes: Boolean(values['yes']),
|
|
182
|
+
};
|
|
183
|
+
const result = runUninstall(uninstallOpts, fs, env);
|
|
184
|
+
if (result.status === 'skipped') {
|
|
185
|
+
stdout('omd: not installed (no changes needed)');
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
for (const r of result.results) {
|
|
189
|
+
if (r.status === 'wrote') {
|
|
190
|
+
stdout(`omd: removed from ${r.path}`);
|
|
191
|
+
}
|
|
192
|
+
else if (r.status === 'skipped') {
|
|
193
|
+
stdout(`omd: ${r.path} — not present`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (result.purged) {
|
|
197
|
+
stdout('omd: cache purged');
|
|
198
|
+
}
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
case 'status': {
|
|
202
|
+
await runStatus(fs, env, stdout);
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
case 'doctor': {
|
|
206
|
+
const docResult = await runDoctor(fs, env, stdout, stderr);
|
|
207
|
+
return docResult.ok ? 0 : 1;
|
|
208
|
+
}
|
|
209
|
+
case 'update': {
|
|
210
|
+
const remaining = argv.slice(argv.indexOf(resolvedCommand) + 1);
|
|
211
|
+
const { values } = parseArgs({
|
|
212
|
+
argv: remaining,
|
|
213
|
+
allowPositionals: true,
|
|
214
|
+
strict: false,
|
|
215
|
+
options: {
|
|
216
|
+
'dry-run': { type: 'boolean', default: false },
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const dryRun = Boolean(values['dry-run']);
|
|
220
|
+
const updateResult = await runUpdate(fs, env, stdout, stderr, { dryRun, latestVersion: opts.latestVersion });
|
|
221
|
+
if (updateResult.status === 'current') {
|
|
222
|
+
stdout('omd: already at latest version');
|
|
223
|
+
}
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
default:
|
|
227
|
+
stderr(`omd: unknown command '${resolvedCommand}'`);
|
|
228
|
+
stderr("Run 'omd --help' for usage.");
|
|
229
|
+
return 2;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
stderr(`omd: ${err.message}`);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
238
|
+
// Only run when executed directly (not imported as a module in tests).
|
|
239
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]?.replace(/\\/g, '/')}`;
|
|
240
|
+
if (isMainModule) {
|
|
241
|
+
const exitCode = await runMain({}, process.argv.slice(2));
|
|
242
|
+
process.exit(exitCode);
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../../src/cli/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"real-fs.d.ts","sourceRoot":"","sources":["../../../src/cli/real-fs.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC,eAAO,MAAM,YAAY,QAAO,KAsB9B,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// src/cli/real-fs.ts — Default `CliFs` adapter backed by `node:fs`.
|
|
3
|
+
//
|
|
4
|
+
// The CLI commands default to the real filesystem in production. Tests inject
|
|
5
|
+
// an in-memory adapter to keep everything deterministic and fast. All methods
|
|
6
|
+
// are sync — the CLI is short-lived and never benefits from async I/O.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmdirSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
9
|
+
export const createRealFs = () => ({
|
|
10
|
+
readFileSync: (path) => readFileSync(path, "utf8"),
|
|
11
|
+
writeFileSync: (path, content) => {
|
|
12
|
+
writeFileSync(path, content);
|
|
13
|
+
},
|
|
14
|
+
renameSync: (from, to) => {
|
|
15
|
+
renameSync(from, to);
|
|
16
|
+
},
|
|
17
|
+
copyFileSync: (from, to) => {
|
|
18
|
+
copyFileSync(from, to);
|
|
19
|
+
},
|
|
20
|
+
unlinkSync: (path) => {
|
|
21
|
+
unlinkSync(path);
|
|
22
|
+
},
|
|
23
|
+
mkdirSync: (path, opts) => {
|
|
24
|
+
mkdirSync(path, opts);
|
|
25
|
+
},
|
|
26
|
+
readdirSync: (path) => readdirSync(path),
|
|
27
|
+
existsSync: (path) => existsSync(path),
|
|
28
|
+
rmdirSync: (path) => {
|
|
29
|
+
rmdirSync(path);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=real-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"real-fs.js","sourceRoot":"","sources":["../../../src/cli/real-fs.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,uEAAuE;AACvE,8EAA8E;AAE9E,OAAO,EACL,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,UAAU,EACV,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,MAAM,CAAC,MAAM,YAAY,GAAG,GAAU,EAAE,CAAC,CAAC;IACxC,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC;IAClD,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE;QAC/B,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/B,CAAC;IACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE;QACvB,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACvB,CAAC;IACD,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE;QACzB,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACzB,CAAC;IACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;QACnB,UAAU,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IACD,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QACxB,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC;IACxC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;IACtC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;QAClB,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch the latest version string from the npm registry.
|
|
3
|
+
* Returns `null` when the registry is unreachable or the response is
|
|
4
|
+
* malformed — callers treat `null` as "can't determine, don't block".
|
|
5
|
+
*/
|
|
6
|
+
export declare const fetchLatestVersion: () => Promise<string | null>;
|
|
7
|
+
/**
|
|
8
|
+
* Return whether the installed version is older than the latest.
|
|
9
|
+
* Unknown versions (null on either side) are never treated as stale.
|
|
10
|
+
*/
|
|
11
|
+
export declare const isStale: (installed: string | null, latest: string | null) => boolean;
|
|
12
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/cli/registry.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,MAAM,GAAG,IAAI,CAShE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,GAClB,WAAW,MAAM,GAAG,IAAI,EACxB,QAAQ,MAAM,GAAG,IAAI,KACpB,OAGF,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// src/cli/registry.ts — npm registry helpers for `omd`.
|
|
3
|
+
//
|
|
4
|
+
// Fetches the latest published version of `opencode-rules-md` from the npm
|
|
5
|
+
// registry. Uses the native `fetch()` API (Node 20+) — no extra dependencies.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
const REGISTRY_URL = "https://registry.npmjs.org/opencode-rules-md/latest";
|
|
8
|
+
/**
|
|
9
|
+
* Fetch the latest version string from the npm registry.
|
|
10
|
+
* Returns `null` when the registry is unreachable or the response is
|
|
11
|
+
* malformed — callers treat `null` as "can't determine, don't block".
|
|
12
|
+
*/
|
|
13
|
+
export const fetchLatestVersion = async () => {
|
|
14
|
+
try {
|
|
15
|
+
const res = await fetch(REGISTRY_URL);
|
|
16
|
+
if (!res.ok)
|
|
17
|
+
return null;
|
|
18
|
+
const data = (await res.json());
|
|
19
|
+
return data.version ?? null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Return whether the installed version is older than the latest.
|
|
27
|
+
* Unknown versions (null on either side) are never treated as stale.
|
|
28
|
+
*/
|
|
29
|
+
export const isStale = (installed, latest) => {
|
|
30
|
+
if (!installed || !latest)
|
|
31
|
+
return false;
|
|
32
|
+
return installed !== latest;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/cli/registry.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wDAAwD;AACxD,EAAE;AACF,2EAA2E;AAC3E,8EAA8E;AAC9E,8EAA8E;AAE9E,MAAM,YAAY,GAAG,qDAAqD,CAAC;AAE3E;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,IAA4B,EAAE;IACnE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyB,CAAC;QACxD,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,SAAwB,EACxB,MAAqB,EACZ,EAAE;IACX,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,SAAS,KAAK,MAAM,CAAC;AAC9B,CAAC,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type CliFs } from './config.js';
|
|
2
|
+
export interface StatusEntry {
|
|
3
|
+
basename: string;
|
|
4
|
+
path: string;
|
|
5
|
+
format: string;
|
|
6
|
+
installed: string | null;
|
|
7
|
+
notInstalled?: boolean;
|
|
8
|
+
otherPlugins: string[];
|
|
9
|
+
latest: string | null;
|
|
10
|
+
isLatest: boolean | null;
|
|
11
|
+
}
|
|
12
|
+
export interface StatusResult {
|
|
13
|
+
configs: StatusEntry[];
|
|
14
|
+
}
|
|
15
|
+
export interface DoctorOptions {
|
|
16
|
+
nodeVersion?: string;
|
|
17
|
+
hasBun?: boolean;
|
|
18
|
+
ruleDirExists?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface DoctorResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
issues: string[];
|
|
23
|
+
warnings: string[];
|
|
24
|
+
info: string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Run the status command: read-only probe for both opencode.json and tui.json.
|
|
28
|
+
* Reports path, format, installed specifier, other plugins, and freshness.
|
|
29
|
+
*/
|
|
30
|
+
export declare const runStatus: (fs: CliFs, env: NodeJS.ProcessEnv, log: (s: string) => void) => Promise<StatusResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Run the doctor command: health checks for the plugin environment.
|
|
33
|
+
* - Node >= 20: issue if below
|
|
34
|
+
* - Bun on PATH: issue if absent
|
|
35
|
+
* - Configs readable: issue if absent or malformed
|
|
36
|
+
* - Plugin shape valid: issue if malformed
|
|
37
|
+
* - Rule dir existence: warning if absent
|
|
38
|
+
* - Config dir writable: issue if not writable
|
|
39
|
+
* - Package freshness: info line about update availability
|
|
40
|
+
*
|
|
41
|
+
* Exit 1 if any issues found.
|
|
42
|
+
*/
|
|
43
|
+
export declare const runDoctor: (fs: CliFs, env: NodeJS.ProcessEnv, log: (s: string) => void, error: (s: string) => void, opts?: DoctorOptions) => Promise<DoctorResult>;
|
|
44
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/cli/status.ts"],"names":[],"mappings":"AAWA,OAAO,EAIL,KAAK,KAAK,EACX,MAAM,aAAa,CAAC;AAKrB,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAID,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAWD;;;GAGG;AACH,eAAO,MAAM,SAAS,GACpB,IAAI,KAAK,EACT,KAAK,MAAM,CAAC,UAAU,EACtB,KAAK,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,KACvB,OAAO,CAAC,YAAY,CA+CtB,CAAC;AAIF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,SAAS,GACpB,IAAI,KAAK,EACT,KAAK,MAAM,CAAC,UAAU,EACtB,KAAK,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EACxB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC1B,OAAM,aAAkB,KACvB,OAAO,CAAC,YAAY,CA6GtB,CAAC"}
|