@wavexzore/sandbox 0.1.0
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/Dockerfile +14 -0
- package/LICENSE +661 -0
- package/NOTICE +3 -0
- package/README.md +153 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/sandbox/cli/install.d.ts +5 -0
- package/dist/sandbox/cli/install.js +335 -0
- package/dist/sandbox/cli/local-store.d.ts +87 -0
- package/dist/sandbox/cli/local-store.js +604 -0
- package/dist/sandbox/cli/opencode-config.d.ts +25 -0
- package/dist/sandbox/cli/opencode-config.js +240 -0
- package/dist/sandbox/cli/path.d.ts +64 -0
- package/dist/sandbox/cli/path.js +127 -0
- package/dist/sandbox/cli/types.d.ts +145 -0
- package/dist/sandbox/cli/types.js +6 -0
- package/dist/sandbox/cli/wavexzore-sandbox.d.ts +65 -0
- package/dist/sandbox/cli/wavexzore-sandbox.js +577 -0
- package/dist/sandbox/core/cli-helper.d.ts +19 -0
- package/dist/sandbox/core/cli-helper.js +64 -0
- package/dist/sandbox/core/docker-archive-utils.d.ts +3 -0
- package/dist/sandbox/core/docker-archive-utils.js +50 -0
- package/dist/sandbox/core/docker-sandbox.d.ts +51 -0
- package/dist/sandbox/core/docker-sandbox.js +675 -0
- package/dist/sandbox/core/edit/filediff.d.ts +16 -0
- package/dist/sandbox/core/edit/filediff.js +21 -0
- package/dist/sandbox/core/edit/index.d.ts +5 -0
- package/dist/sandbox/core/edit/index.js +5 -0
- package/dist/sandbox/core/edit/line-endings.d.ts +4 -0
- package/dist/sandbox/core/edit/line-endings.js +10 -0
- package/dist/sandbox/core/edit/lock.d.ts +1 -0
- package/dist/sandbox/core/edit/lock.js +18 -0
- package/dist/sandbox/core/edit/replace.d.ts +10 -0
- package/dist/sandbox/core/edit/replace.js +14 -0
- package/dist/sandbox/core/edit/replacers.d.ts +15 -0
- package/dist/sandbox/core/edit/replacers.js +241 -0
- package/dist/sandbox/core/logger.d.ts +15 -0
- package/dist/sandbox/core/logger.js +59 -0
- package/dist/sandbox/core/lsp/client.d.ts +63 -0
- package/dist/sandbox/core/lsp/client.js +533 -0
- package/dist/sandbox/core/lsp/config.d.ts +13 -0
- package/dist/sandbox/core/lsp/config.js +36 -0
- package/dist/sandbox/core/lsp/diagnostics.d.ts +12 -0
- package/dist/sandbox/core/lsp/diagnostics.js +65 -0
- package/dist/sandbox/core/lsp/index.d.ts +4 -0
- package/dist/sandbox/core/lsp/index.js +4 -0
- package/dist/sandbox/core/lsp/language.d.ts +24 -0
- package/dist/sandbox/core/lsp/language.js +249 -0
- package/dist/sandbox/core/lsp/manager.d.ts +77 -0
- package/dist/sandbox/core/lsp/manager.js +237 -0
- package/dist/sandbox/core/lsp/tooling.d.ts +14 -0
- package/dist/sandbox/core/lsp/tooling.js +78 -0
- package/dist/sandbox/core/patch-parser.d.ts +23 -0
- package/dist/sandbox/core/patch-parser.js +248 -0
- package/dist/sandbox/core/path-map.d.ts +9 -0
- package/dist/sandbox/core/path-map.js +73 -0
- package/dist/sandbox/core/project-data-storage.d.ts +42 -0
- package/dist/sandbox/core/project-data-storage.js +167 -0
- package/dist/sandbox/core/read/binary.d.ts +4 -0
- package/dist/sandbox/core/read/binary.js +80 -0
- package/dist/sandbox/core/read/format.d.ts +38 -0
- package/dist/sandbox/core/read/format.js +85 -0
- package/dist/sandbox/core/read/index.d.ts +3 -0
- package/dist/sandbox/core/read/index.js +3 -0
- package/dist/sandbox/core/read/permissions.d.ts +7 -0
- package/dist/sandbox/core/read/permissions.js +13 -0
- package/dist/sandbox/core/session-manager.d.ts +29 -0
- package/dist/sandbox/core/session-manager.js +338 -0
- package/dist/sandbox/core/shell/config.d.ts +7 -0
- package/dist/sandbox/core/shell/config.js +82 -0
- package/dist/sandbox/core/shell/output.d.ts +35 -0
- package/dist/sandbox/core/shell/output.js +80 -0
- package/dist/sandbox/core/shell/parser.d.ts +7 -0
- package/dist/sandbox/core/shell/parser.js +122 -0
- package/dist/sandbox/core/shell/permissions.d.ts +13 -0
- package/dist/sandbox/core/shell/permissions.js +33 -0
- package/dist/sandbox/core/shell/workdir.d.ts +4 -0
- package/dist/sandbox/core/shell/workdir.js +19 -0
- package/dist/sandbox/core/stream-utils.d.ts +23 -0
- package/dist/sandbox/core/stream-utils.js +97 -0
- package/dist/sandbox/core/toast.d.ts +47 -0
- package/dist/sandbox/core/toast.js +73 -0
- package/dist/sandbox/core/types.d.ts +159 -0
- package/dist/sandbox/core/types.js +11 -0
- package/dist/sandbox/core/write/bom.d.ts +8 -0
- package/dist/sandbox/core/write/bom.js +15 -0
- package/dist/sandbox/core/write/config.d.ts +14 -0
- package/dist/sandbox/core/write/config.js +188 -0
- package/dist/sandbox/core/write/diagnostics.d.ts +19 -0
- package/dist/sandbox/core/write/diagnostics.js +120 -0
- package/dist/sandbox/core/write/diff.d.ts +7 -0
- package/dist/sandbox/core/write/diff.js +21 -0
- package/dist/sandbox/core/write/formatter.d.ts +16 -0
- package/dist/sandbox/core/write/formatter.js +51 -0
- package/dist/sandbox/core/write/index.d.ts +6 -0
- package/dist/sandbox/core/write/index.js +5 -0
- package/dist/sandbox/core/write/permissions.d.ts +13 -0
- package/dist/sandbox/core/write/permissions.js +19 -0
- package/dist/sandbox/core/write/pipeline.d.ts +48 -0
- package/dist/sandbox/core/write/pipeline.js +229 -0
- package/dist/sandbox/core/write/read-tracker.d.ts +13 -0
- package/dist/sandbox/core/write/read-tracker.js +30 -0
- package/dist/sandbox/git/host-git-manager.d.ts +40 -0
- package/dist/sandbox/git/host-git-manager.js +278 -0
- package/dist/sandbox/git/index.d.ts +5 -0
- package/dist/sandbox/git/index.js +5 -0
- package/dist/sandbox/git/sandbox-git-manager.d.ts +14 -0
- package/dist/sandbox/git/sandbox-git-manager.js +54 -0
- package/dist/sandbox/git/session-git-manager.d.ts +18 -0
- package/dist/sandbox/git/session-git-manager.js +85 -0
- package/dist/sandbox/index.d.ts +205 -0
- package/dist/sandbox/index.js +70 -0
- package/dist/sandbox/plugins/custom-tools.d.ts +203 -0
- package/dist/sandbox/plugins/custom-tools.js +15 -0
- package/dist/sandbox/plugins/session-events.d.ts +10 -0
- package/dist/sandbox/plugins/session-events.js +56 -0
- package/dist/sandbox/plugins/system-transform.d.ts +10 -0
- package/dist/sandbox/plugins/system-transform.js +23 -0
- package/dist/sandbox/tools/bash-output.d.ts +17 -0
- package/dist/sandbox/tools/bash-output.js +35 -0
- package/dist/sandbox/tools/bash-status.d.ts +13 -0
- package/dist/sandbox/tools/bash-status.js +29 -0
- package/dist/sandbox/tools/bash-stop.d.ts +13 -0
- package/dist/sandbox/tools/bash-stop.js +28 -0
- package/dist/sandbox/tools/bash.d.ts +26 -0
- package/dist/sandbox/tools/bash.js +120 -0
- package/dist/sandbox/tools/edit.d.ts +20 -0
- package/dist/sandbox/tools/edit.js +87 -0
- package/dist/sandbox/tools/get-preview-url.d.ts +17 -0
- package/dist/sandbox/tools/get-preview-url.js +16 -0
- package/dist/sandbox/tools/glob.d.ts +17 -0
- package/dist/sandbox/tools/glob.js +23 -0
- package/dist/sandbox/tools/grep.d.ts +17 -0
- package/dist/sandbox/tools/grep.js +23 -0
- package/dist/sandbox/tools/ls.d.ts +17 -0
- package/dist/sandbox/tools/ls.js +21 -0
- package/dist/sandbox/tools/lsp.d.ts +41 -0
- package/dist/sandbox/tools/lsp.js +198 -0
- package/dist/sandbox/tools/multiedit.d.ts +24 -0
- package/dist/sandbox/tools/multiedit.js +83 -0
- package/dist/sandbox/tools/patch.d.ts +14 -0
- package/dist/sandbox/tools/patch.js +260 -0
- package/dist/sandbox/tools/read.d.ts +22 -0
- package/dist/sandbox/tools/read.js +105 -0
- package/dist/sandbox/tools/write.d.ts +16 -0
- package/dist/sandbox/tools/write.js +27 -0
- package/dist/sandbox/tools.d.ts +200 -0
- package/dist/sandbox/tools.js +43 -0
- package/package.json +55 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from 'fs';
|
|
4
|
+
import { basename, dirname, join, relative, resolve } from 'path';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
7
|
+
import { defaultHome, isDirectoryOnPath, pathHintForPlatform, powershellSingleQuote, resolveWavexzoreSandboxBinDir, resolveWavexzoreSandboxHomeDir, runtimeEnv, runtimePlatform, shellProfilePath, shellQuote, } from './path.js';
|
|
8
|
+
import { OPENCODE_HELPER_CLI, WAVEXZORE_SANDBOX_CLI, WAVEXZORE_SANDBOX_LOADER_PACKAGE, WAVEXZORE_SANDBOX_PACKAGE, WAVEXZORE_SANDBOX_PLUGIN_ID, } from './types.js';
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
export function nowIso(now = Date.now()) {
|
|
12
|
+
return new Date(now).toISOString();
|
|
13
|
+
}
|
|
14
|
+
function ensureParent(path) {
|
|
15
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
export function writeFileAtomic(path, contents, dryRun) {
|
|
18
|
+
if (dryRun)
|
|
19
|
+
return;
|
|
20
|
+
ensureParent(path);
|
|
21
|
+
const tmp = join(dirname(path), `.${basename(path)}-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`);
|
|
22
|
+
try {
|
|
23
|
+
writeFileSync(tmp, contents, 'utf8');
|
|
24
|
+
renameSync(tmp, path);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
try {
|
|
28
|
+
rmSync(tmp, { force: true });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Ignore best-effort cleanup failures.
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function writeJsonAtomic(path, value, dryRun) {
|
|
37
|
+
writeFileAtomic(path, `${JSON.stringify(value, null, 2)}\n`, dryRun);
|
|
38
|
+
}
|
|
39
|
+
export function statusForText(path, desired) {
|
|
40
|
+
if (!existsSync(path))
|
|
41
|
+
return 'created';
|
|
42
|
+
return readFileSync(path, 'utf8') === desired ? 'existing' : 'updated';
|
|
43
|
+
}
|
|
44
|
+
function combineInstallStatus(statuses) {
|
|
45
|
+
return statuses.includes('created') ? 'created' : statuses.includes('updated') ? 'updated' : 'existing';
|
|
46
|
+
}
|
|
47
|
+
function combineUninstallStatus(statuses) {
|
|
48
|
+
if (statuses.includes('removed'))
|
|
49
|
+
return 'removed';
|
|
50
|
+
if (statuses.includes('updated'))
|
|
51
|
+
return 'updated';
|
|
52
|
+
if (statuses.includes('kept'))
|
|
53
|
+
return 'kept';
|
|
54
|
+
if (statuses.includes('skipped'))
|
|
55
|
+
return 'skipped';
|
|
56
|
+
if (statuses.includes('unchanged'))
|
|
57
|
+
return 'unchanged';
|
|
58
|
+
return 'missing';
|
|
59
|
+
}
|
|
60
|
+
export function assertSafeVersion(version) {
|
|
61
|
+
if (!version || !/^[0-9A-Za-z._+-]+$/.test(version) || version.includes('..')) {
|
|
62
|
+
throw new Error(`Unsafe package version for local store path: ${version}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function resolveWavexzoreSandboxManifestPath(storeDir) {
|
|
66
|
+
return join(storeDir, 'manifest.json');
|
|
67
|
+
}
|
|
68
|
+
export function resolveWavexzoreSandboxLoaderDir(storeDir) {
|
|
69
|
+
return join(storeDir, 'opencode-plugin');
|
|
70
|
+
}
|
|
71
|
+
export function resolveWavexzoreSandboxVersionDir(storeDir, version) {
|
|
72
|
+
assertSafeVersion(version);
|
|
73
|
+
return join(storeDir, 'versions', version);
|
|
74
|
+
}
|
|
75
|
+
export function pathToPluginFileSpec(loaderDir) {
|
|
76
|
+
return pathToFileURL(loaderDir).href;
|
|
77
|
+
}
|
|
78
|
+
export function resolveWavexzoreSandboxBinShimPaths(binDir) {
|
|
79
|
+
return {
|
|
80
|
+
js: join(binDir, `${WAVEXZORE_SANDBOX_CLI}.mjs`),
|
|
81
|
+
unix: join(binDir, WAVEXZORE_SANDBOX_CLI),
|
|
82
|
+
cmd: join(binDir, `${WAVEXZORE_SANDBOX_CLI}.cmd`),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function resolveOpenCodeHelperPath(binDir, platform = runtimePlatform()) {
|
|
86
|
+
return join(binDir, platform === 'win32' ? `${OPENCODE_HELPER_CLI}.cmd` : OPENCODE_HELPER_CLI);
|
|
87
|
+
}
|
|
88
|
+
function resolveOpenCodeHelperMjsPath(binDir) {
|
|
89
|
+
return join(binDir, `${OPENCODE_HELPER_CLI}.mjs`);
|
|
90
|
+
}
|
|
91
|
+
function readPackageJson(packageRoot) {
|
|
92
|
+
return JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
93
|
+
}
|
|
94
|
+
function packageRootFrom(startDir) {
|
|
95
|
+
let current = startDir;
|
|
96
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
97
|
+
const pkg = join(current, 'package.json');
|
|
98
|
+
if (existsSync(pkg)) {
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(readFileSync(pkg, 'utf8'));
|
|
101
|
+
if (parsed.name === WAVEXZORE_SANDBOX_PACKAGE)
|
|
102
|
+
return current;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Ignore best-effort cleanup failures.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const parent = dirname(current);
|
|
109
|
+
if (parent === current)
|
|
110
|
+
return undefined;
|
|
111
|
+
current = parent;
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
export function resolveCurrentPackageRoot(explicit, env) {
|
|
116
|
+
if (explicit)
|
|
117
|
+
return explicit;
|
|
118
|
+
const envRoot = runtimeEnv(env).WAVEXZORE_SANDBOX_PACKAGE_ROOT;
|
|
119
|
+
if (envRoot)
|
|
120
|
+
return envRoot;
|
|
121
|
+
const fromModule = packageRootFrom(moduleDir);
|
|
122
|
+
if (fromModule)
|
|
123
|
+
return fromModule;
|
|
124
|
+
const fromCwd = packageRootFrom(process.cwd());
|
|
125
|
+
if (fromCwd)
|
|
126
|
+
return fromCwd;
|
|
127
|
+
throw new Error(`Cannot locate ${WAVEXZORE_SANDBOX_PACKAGE} package root`);
|
|
128
|
+
}
|
|
129
|
+
function assertRequiredPackageFiles(packageRoot, version) {
|
|
130
|
+
const packageJsonPath = join(packageRoot, 'package.json');
|
|
131
|
+
const pluginEntry = join(packageRoot, 'dist', 'index.js');
|
|
132
|
+
const cliEntry = join(packageRoot, 'dist', 'sandbox', 'cli', `${WAVEXZORE_SANDBOX_CLI}.js`);
|
|
133
|
+
if (!existsSync(packageJsonPath))
|
|
134
|
+
throw new Error(`Missing package.json in ${packageRoot}`);
|
|
135
|
+
const parsed = readPackageJson(packageRoot);
|
|
136
|
+
if (parsed.name !== WAVEXZORE_SANDBOX_PACKAGE)
|
|
137
|
+
throw new Error(`Invalid package name: expected ${WAVEXZORE_SANDBOX_PACKAGE}, got ${String(parsed.name)}`);
|
|
138
|
+
if (parsed.version !== version)
|
|
139
|
+
throw new Error(`Invalid package version metadata: expected ${version}, got ${String(parsed.version)}`);
|
|
140
|
+
if (!existsSync(pluginEntry)) {
|
|
141
|
+
throw new Error(`Built plugin entry not found at ${pluginEntry}. Run npm --workspace @wavexzore/sandbox run build before installing from source.`);
|
|
142
|
+
}
|
|
143
|
+
if (!existsSync(cliEntry)) {
|
|
144
|
+
throw new Error(`Built CLI entry not found at ${cliEntry}. Run npm --workspace @wavexzore/sandbox run build before installing from source.`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function directoryFileHashes(root) {
|
|
148
|
+
const hashes = {};
|
|
149
|
+
function visit(dir, prefix = '') {
|
|
150
|
+
for (const name of readdirSync(dir).sort()) {
|
|
151
|
+
const path = join(dir, name);
|
|
152
|
+
const rel = prefix ? `${prefix}/${name}` : name;
|
|
153
|
+
const stat = statSync(path);
|
|
154
|
+
if (stat.isDirectory())
|
|
155
|
+
visit(path, rel);
|
|
156
|
+
else if (stat.isFile())
|
|
157
|
+
hashes[rel] = createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
visit(root);
|
|
161
|
+
return hashes;
|
|
162
|
+
}
|
|
163
|
+
function sameDirectoryContent(left, right) {
|
|
164
|
+
if (!existsSync(left) || !existsSync(right))
|
|
165
|
+
return false;
|
|
166
|
+
return JSON.stringify(directoryFileHashes(left)) === JSON.stringify(directoryFileHashes(right));
|
|
167
|
+
}
|
|
168
|
+
function copyIfExists(source, target) {
|
|
169
|
+
if (!existsSync(source))
|
|
170
|
+
return;
|
|
171
|
+
cpSync(source, target, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
function copyPackageToStaging(packageRoot, stagingPath) {
|
|
174
|
+
mkdirSync(stagingPath, { recursive: true });
|
|
175
|
+
copyIfExists(join(packageRoot, 'package.json'), join(stagingPath, 'package.json'));
|
|
176
|
+
copyIfExists(join(packageRoot, 'dist'), join(stagingPath, 'dist'));
|
|
177
|
+
copyIfExists(join(packageRoot, 'README.md'), join(stagingPath, 'README.md'));
|
|
178
|
+
copyIfExists(join(packageRoot, 'Dockerfile'), join(stagingPath, 'Dockerfile'));
|
|
179
|
+
copyIfExists(join(packageRoot, 'LICENSE'), join(stagingPath, 'LICENSE'));
|
|
180
|
+
copyIfExists(join(packageRoot, 'NOTICE'), join(stagingPath, 'NOTICE'));
|
|
181
|
+
}
|
|
182
|
+
function packageNamesFromDependencies(packageJsonPath) {
|
|
183
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
184
|
+
return Object.keys(parsed.dependencies ?? {});
|
|
185
|
+
}
|
|
186
|
+
function packageNameFromPackageJson(packageJsonPath) {
|
|
187
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
188
|
+
if (!parsed.name)
|
|
189
|
+
throw new Error(`Dependency package at ${packageJsonPath} is missing a name`);
|
|
190
|
+
return parsed.name;
|
|
191
|
+
}
|
|
192
|
+
function dependencyTargetPath(nodeModulesDir, packageName) {
|
|
193
|
+
if (packageName.startsWith('@')) {
|
|
194
|
+
const [scope, name] = packageName.split('/');
|
|
195
|
+
if (!scope || !name)
|
|
196
|
+
throw new Error(`Invalid scoped dependency name: ${packageName}`);
|
|
197
|
+
return join(nodeModulesDir, scope, name);
|
|
198
|
+
}
|
|
199
|
+
return join(nodeModulesDir, packageName);
|
|
200
|
+
}
|
|
201
|
+
function packageJsonFromResolvedEntry(entry, packageName) {
|
|
202
|
+
let current = dirname(entry);
|
|
203
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
204
|
+
const candidate = join(current, 'package.json');
|
|
205
|
+
if (existsSync(candidate)) {
|
|
206
|
+
try {
|
|
207
|
+
if (packageNameFromPackageJson(candidate) === packageName)
|
|
208
|
+
return candidate;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Continue walking when a package.json cannot be parsed.
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const parent = dirname(current);
|
|
215
|
+
if (parent === current)
|
|
216
|
+
return undefined;
|
|
217
|
+
current = parent;
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
function packageJsonFromNodeModules(packageName, fromDir) {
|
|
222
|
+
let current = resolve(fromDir);
|
|
223
|
+
for (let depth = 0; depth < 16; depth += 1) {
|
|
224
|
+
const candidate = join(current, 'node_modules', ...packageName.split('/'), 'package.json');
|
|
225
|
+
if (existsSync(candidate))
|
|
226
|
+
return candidate;
|
|
227
|
+
const parent = dirname(current);
|
|
228
|
+
if (parent === current)
|
|
229
|
+
return undefined;
|
|
230
|
+
current = parent;
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
function resolveDependencyPackageJson(packageName, fromDir) {
|
|
235
|
+
const fromNodeModules = packageJsonFromNodeModules(packageName, fromDir);
|
|
236
|
+
if (fromNodeModules)
|
|
237
|
+
return fromNodeModules;
|
|
238
|
+
try {
|
|
239
|
+
return require.resolve(`${packageName}/package.json`, { paths: [fromDir] });
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// Some packages intentionally hide package.json through exports. Resolve the package entry instead.
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const entry = require.resolve(packageName, { paths: [fromDir] });
|
|
246
|
+
const packageJsonPath = packageJsonFromResolvedEntry(entry, packageName);
|
|
247
|
+
if (packageJsonPath)
|
|
248
|
+
return packageJsonPath;
|
|
249
|
+
throw new Error(`could not find package.json from resolved entry ${entry}`);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
throw new Error(`Cannot resolve dependency ${packageName} from ${fromDir}: ${error instanceof Error ? error.message : String(error)}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function copyDependencyClosure(args) {
|
|
256
|
+
for (const dependencyName of packageNamesFromDependencies(args.packageJsonPath)) {
|
|
257
|
+
const dependencyPackageJson = resolveDependencyPackageJson(dependencyName, args.fromDir);
|
|
258
|
+
const actualName = packageNameFromPackageJson(dependencyPackageJson);
|
|
259
|
+
const packageRoot = dirname(dependencyPackageJson);
|
|
260
|
+
const seenKey = `${actualName}:${packageRoot}`;
|
|
261
|
+
if (args.seen.has(seenKey))
|
|
262
|
+
continue;
|
|
263
|
+
args.seen.add(seenKey);
|
|
264
|
+
const target = dependencyTargetPath(args.nodeModulesDir, actualName);
|
|
265
|
+
if (!existsSync(target)) {
|
|
266
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
267
|
+
cpSync(packageRoot, target, { recursive: true, force: true, dereference: true });
|
|
268
|
+
rmSync(join(target, 'node_modules'), { recursive: true, force: true });
|
|
269
|
+
}
|
|
270
|
+
copyDependencyClosure({
|
|
271
|
+
packageJsonPath: dependencyPackageJson,
|
|
272
|
+
fromDir: packageRoot,
|
|
273
|
+
nodeModulesDir: args.nodeModulesDir,
|
|
274
|
+
seen: args.seen,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function prepareStoreDependencies(packageRoot, storeDir, dryRun) {
|
|
279
|
+
if (dryRun)
|
|
280
|
+
return;
|
|
281
|
+
const packageJsonPath = join(packageRoot, 'package.json');
|
|
282
|
+
const nodeModulesDir = join(storeDir, 'node_modules');
|
|
283
|
+
mkdirSync(nodeModulesDir, { recursive: true });
|
|
284
|
+
copyDependencyClosure({ packageJsonPath, fromDir: packageRoot, nodeModulesDir, seen: new Set() });
|
|
285
|
+
}
|
|
286
|
+
export function readCurrentPackageVersion(packageRoot = resolveCurrentPackageRoot()) {
|
|
287
|
+
const parsed = readPackageJson(packageRoot);
|
|
288
|
+
if (typeof parsed.version !== 'string' || parsed.version.length === 0) {
|
|
289
|
+
throw new Error(`Invalid package version in ${join(packageRoot, 'package.json')}`);
|
|
290
|
+
}
|
|
291
|
+
return parsed.version;
|
|
292
|
+
}
|
|
293
|
+
export function prepareCurrentPackageVersion(args) {
|
|
294
|
+
const packageRoot = resolveCurrentPackageRoot(args.packageRoot, args.env);
|
|
295
|
+
const version = readCurrentPackageVersion(packageRoot);
|
|
296
|
+
assertSafeVersion(version);
|
|
297
|
+
const versionPath = resolveWavexzoreSandboxVersionDir(args.storeDir, version);
|
|
298
|
+
if (existsSync(versionPath)) {
|
|
299
|
+
assertRequiredPackageFiles(versionPath, version);
|
|
300
|
+
if (!args.dryRun)
|
|
301
|
+
prepareStoreDependencies(packageRoot, args.storeDir, args.dryRun);
|
|
302
|
+
if (args.dryRun)
|
|
303
|
+
return { version, versionPath, versionStatus: 'existing' };
|
|
304
|
+
}
|
|
305
|
+
if (args.dryRun) {
|
|
306
|
+
assertRequiredPackageFiles(packageRoot, version);
|
|
307
|
+
return { version, versionPath, versionStatus: existsSync(versionPath) ? 'existing' : 'created' };
|
|
308
|
+
}
|
|
309
|
+
assertRequiredPackageFiles(packageRoot, version);
|
|
310
|
+
prepareStoreDependencies(packageRoot, args.storeDir, args.dryRun);
|
|
311
|
+
const stagingPath = join(args.storeDir, 'tmp', `install-${version}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
312
|
+
rmSync(stagingPath, { recursive: true, force: true });
|
|
313
|
+
copyPackageToStaging(packageRoot, stagingPath);
|
|
314
|
+
assertRequiredPackageFiles(stagingPath, version);
|
|
315
|
+
mkdirSync(dirname(versionPath), { recursive: true });
|
|
316
|
+
if (existsSync(versionPath)) {
|
|
317
|
+
if (sameDirectoryContent(stagingPath, versionPath)) {
|
|
318
|
+
rmSync(stagingPath, { recursive: true, force: true });
|
|
319
|
+
return { version, versionPath, versionStatus: 'existing' };
|
|
320
|
+
}
|
|
321
|
+
rmSync(versionPath, { recursive: true, force: true });
|
|
322
|
+
renameSync(stagingPath, versionPath);
|
|
323
|
+
return { version, versionPath, versionStatus: 'updated' };
|
|
324
|
+
}
|
|
325
|
+
renameSync(stagingPath, versionPath);
|
|
326
|
+
return { version, versionPath, versionStatus: 'created' };
|
|
327
|
+
}
|
|
328
|
+
function localLoaderPackageJson() {
|
|
329
|
+
return `${JSON.stringify({
|
|
330
|
+
name: WAVEXZORE_SANDBOX_LOADER_PACKAGE,
|
|
331
|
+
type: 'module',
|
|
332
|
+
main: './index.mjs',
|
|
333
|
+
exports: { '.': './index.mjs' },
|
|
334
|
+
}, null, 2)}\n`;
|
|
335
|
+
}
|
|
336
|
+
function localLoaderIndex() {
|
|
337
|
+
return `import { readFileSync } from "node:fs"\nimport { dirname, join } from "node:path"\nimport { fileURLToPath, pathToFileURL } from "node:url"\n\nconst root = dirname(dirname(fileURLToPath(import.meta.url)))\nconst manifest = JSON.parse(readFileSync(join(root, "manifest.json"), "utf8"))\nconst entry = join(root, "versions", manifest.activeVersion, "dist", "index.js")\nconst mod = await import(pathToFileURL(entry).href)\nconst plugin = mod.default?.default ?? mod.default ?? mod\nexport default plugin\n`;
|
|
338
|
+
}
|
|
339
|
+
export function writeLocalLoader(loaderDir, dryRun) {
|
|
340
|
+
const packageJsonPath = join(loaderDir, 'package.json');
|
|
341
|
+
const indexPath = join(loaderDir, 'index.mjs');
|
|
342
|
+
const packageStatus = statusForText(packageJsonPath, localLoaderPackageJson());
|
|
343
|
+
const indexStatus = statusForText(indexPath, localLoaderIndex());
|
|
344
|
+
if (!dryRun) {
|
|
345
|
+
mkdirSync(loaderDir, { recursive: true });
|
|
346
|
+
writeFileAtomic(packageJsonPath, localLoaderPackageJson());
|
|
347
|
+
writeFileAtomic(indexPath, localLoaderIndex());
|
|
348
|
+
}
|
|
349
|
+
return combineInstallStatus([packageStatus, indexStatus]);
|
|
350
|
+
}
|
|
351
|
+
export function readWavexzoreSandboxManifest(storeDir) {
|
|
352
|
+
const manifestPath = resolveWavexzoreSandboxManifestPath(storeDir);
|
|
353
|
+
if (!existsSync(manifestPath))
|
|
354
|
+
return undefined;
|
|
355
|
+
const parsed = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
356
|
+
if (parsed.schemaVersion !== 1 ||
|
|
357
|
+
parsed.packageName !== WAVEXZORE_SANDBOX_PACKAGE ||
|
|
358
|
+
parsed.pluginId !== WAVEXZORE_SANDBOX_PLUGIN_ID) {
|
|
359
|
+
throw new Error(`Invalid Wavexzore Sandbox manifest at ${manifestPath}`);
|
|
360
|
+
}
|
|
361
|
+
if (typeof parsed.activeVersion !== 'string')
|
|
362
|
+
throw new Error(`Invalid activeVersion in ${manifestPath}`);
|
|
363
|
+
return parsed;
|
|
364
|
+
}
|
|
365
|
+
export function nextManifest(args) {
|
|
366
|
+
const installedVersions = Array.from(new Set([...(args.current?.installedVersions ?? []), args.activeVersion])).sort();
|
|
367
|
+
const now = nowIso(args.now ?? Date.now());
|
|
368
|
+
return {
|
|
369
|
+
schemaVersion: 1,
|
|
370
|
+
pluginId: WAVEXZORE_SANDBOX_PLUGIN_ID,
|
|
371
|
+
packageName: WAVEXZORE_SANDBOX_PACKAGE,
|
|
372
|
+
activeVersion: args.activeVersion,
|
|
373
|
+
installedVersions,
|
|
374
|
+
channel: args.channel ?? args.current?.channel ?? 'latest',
|
|
375
|
+
installedAt: args.current?.installedAt ?? now,
|
|
376
|
+
updatedAt: now,
|
|
377
|
+
opencodeConfigPath: args.configPath,
|
|
378
|
+
opencodePluginSpec: args.pluginSpec,
|
|
379
|
+
cliPath: args.cliPath,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
export function writeManifest(manifestPath, manifest, dryRun) {
|
|
383
|
+
const desired = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
384
|
+
const status = statusForText(manifestPath, desired);
|
|
385
|
+
writeFileAtomic(manifestPath, desired, dryRun);
|
|
386
|
+
return status;
|
|
387
|
+
}
|
|
388
|
+
function stableCliShimMjs(storeDir) {
|
|
389
|
+
return `#!/usr/bin/env node\nimport { spawnSync } from "node:child_process"\nimport { existsSync, readFileSync } from "node:fs"\nimport { join } from "node:path"\n\nconst storeDir = ${JSON.stringify(storeDir)}\nconst manifestPath = join(storeDir, "manifest.json")\n\nfunction fail(message) {\n console.error(message)\n process.exit(1)\n}\n\nif (!existsSync(manifestPath)) fail(\`Wavexzore Sandbox manifest not found: \${manifestPath}\`)\n\nlet manifest\ntry {\n manifest = JSON.parse(readFileSync(manifestPath, "utf8"))\n} catch (error) {\n fail(\`Failed to read Wavexzore Sandbox manifest: \${error instanceof Error ? error.message : String(error)}\`)\n}\n\nif (!manifest || typeof manifest.activeVersion !== "string" || manifest.activeVersion.length === 0) {\n fail(\`Invalid Wavexzore Sandbox manifest: \${manifestPath}\`)\n}\n\nconst cli = join(storeDir, "versions", manifest.activeVersion, "dist", "sandbox", "cli", "${WAVEXZORE_SANDBOX_CLI}.js")\nif (!existsSync(cli)) fail(\`Wavexzore Sandbox CLI entry not found for activeVersion=\${manifest.activeVersion}: \${cli}\`)\n\nconst result = spawnSync(process.execPath, [cli, ...process.argv.slice(2)], { stdio: "inherit", shell: false })\nif (result.error) fail(\`Failed to start Wavexzore Sandbox CLI: \${result.error.message}\`)\nprocess.exit(result.status ?? 1)\n`;
|
|
390
|
+
}
|
|
391
|
+
function stableCliShimUnix() {
|
|
392
|
+
return `#!/usr/bin/env sh\nset -eu\nSCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)\nexec node "$SCRIPT_DIR/${WAVEXZORE_SANDBOX_CLI}.mjs" "$@"\n`;
|
|
393
|
+
}
|
|
394
|
+
function stableCliShimCmd() {
|
|
395
|
+
return `@echo off\r\nnode "%~dp0${WAVEXZORE_SANDBOX_CLI}.mjs" %*\r\nexit /b %ERRORLEVEL%\r\n`;
|
|
396
|
+
}
|
|
397
|
+
function stableOpenCodeHelperShimMjs(storeDir) {
|
|
398
|
+
return `#!/usr/bin/env node\nimport { spawnSync } from "node:child_process"\nimport { existsSync, readFileSync } from "node:fs"\nimport { dirname, join } from "node:path"\nimport { fileURLToPath } from "node:url"\n\nconst scriptDir = dirname(fileURLToPath(import.meta.url))\nconst storeDir = ${JSON.stringify(storeDir)}\nconst manifestPath = join(storeDir, "manifest.json")\nconst wavexzoreCli = join(scriptDir, "${WAVEXZORE_SANDBOX_CLI}.mjs")\n\nfunction readManifest() {\n if (!existsSync(manifestPath)) return undefined\n try {\n return JSON.parse(readFileSync(manifestPath, "utf8"))\n } catch (error) {\n return { error: error instanceof Error ? error.message : String(error) }\n }\n}\n\nfunction json(payload, status = 0) {\n console.log(JSON.stringify(payload, null, 2))\n process.exit(status)\n}\n\nfunction opencode(args) {\n const [command] = args\n const manifest = readManifest()\n const activeVersion = typeof manifest?.activeVersion === "string" ? manifest.activeVersion : undefined\n const cliEntry = activeVersion\n ? join(storeDir, "versions", activeVersion, "dist", "sandbox", "cli", "${WAVEXZORE_SANDBOX_CLI}.js")\n : undefined\n const checks = [\n {\n name: "manifest",\n status: manifest && !manifest.error ? "passed" : "failed",\n path: manifestPath,\n message: manifest?.error ?? (manifest ? "manifest exists" : "manifest is missing"),\n },\n {\n name: "wavexzore-cli-shim",\n status: existsSync(wavexzoreCli) ? "passed" : "failed",\n path: wavexzoreCli,\n message: existsSync(wavexzoreCli) ? "stable CLI shim exists" : "stable CLI shim is missing",\n },\n {\n name: "active-cli-entry",\n status: cliEntry && existsSync(cliEntry) ? "passed" : "failed",\n path: cliEntry,\n message: cliEntry && existsSync(cliEntry) ? "active CLI entry exists" : "active CLI entry is missing",\n },\n ]\n const ok = checks.every((check) => check.status === "passed")\n\n if (command === "doctor") {\n json({\n ok,\n data: {\n storeDir,\n manifestPath,\n cliPath: manifest?.cliPath,\n activeVersion,\n checks,\n },\n error: ok\n ? undefined\n : {\n code: "OPENCODE_HELPER_FAILED",\n message: "OpenCode helper installation is incomplete",\n },\n }, ok ? 0 : 1)\n }\n\n if (command === "version") {\n json({ ok: true, data: { activeVersion, manifestPath, cliPath: manifest?.cliPath } })\n }\n\n console.error(\`Unknown OpenCode helper command: \${command ?? ""}\`)\n process.exit(2)\n}\n\nif (process.argv[2] === "opencode") opencode(process.argv.slice(3))\n\nconst result = spawnSync(process.execPath, [wavexzoreCli, ...process.argv.slice(2)], { stdio: "inherit", shell: false })\nif (result.error) {\n console.error(\`Failed to start Wavexzore Sandbox CLI: \${result.error.message}\`)\n process.exit(1)\n}\nprocess.exit(result.status ?? 1)\n`;
|
|
399
|
+
}
|
|
400
|
+
function stableOpenCodeHelperShimUnix() {
|
|
401
|
+
return `#!/usr/bin/env sh\nset -eu\nSCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)\nexec node "$SCRIPT_DIR/${OPENCODE_HELPER_CLI}.mjs" "$@"\n`;
|
|
402
|
+
}
|
|
403
|
+
function stableOpenCodeHelperShimCmd() {
|
|
404
|
+
return `@echo off\r\nnode "%~dp0${OPENCODE_HELPER_CLI}.mjs" %*\r\nexit /b %ERRORLEVEL%\r\n`;
|
|
405
|
+
}
|
|
406
|
+
export function writeWavexzoreSandboxBinShims(args) {
|
|
407
|
+
if (args.enabled === false)
|
|
408
|
+
return { binDir: args.binDir, binStatus: 'skipped' };
|
|
409
|
+
const platform = runtimePlatform(args.platform);
|
|
410
|
+
const paths = resolveWavexzoreSandboxBinShimPaths(args.binDir);
|
|
411
|
+
const helperPath = resolveOpenCodeHelperPath(args.binDir, platform);
|
|
412
|
+
const helperMjsPath = resolveOpenCodeHelperMjsPath(args.binDir);
|
|
413
|
+
const entries = [
|
|
414
|
+
{ path: paths.js, contents: stableCliShimMjs(args.storeDir), executable: true },
|
|
415
|
+
{ path: paths.unix, contents: stableCliShimUnix(), executable: true },
|
|
416
|
+
{ path: paths.cmd, contents: stableCliShimCmd(), executable: false },
|
|
417
|
+
{ path: helperMjsPath, contents: stableOpenCodeHelperShimMjs(args.storeDir), executable: true },
|
|
418
|
+
{
|
|
419
|
+
path: helperPath,
|
|
420
|
+
contents: platform === 'win32' ? stableOpenCodeHelperShimCmd() : stableOpenCodeHelperShimUnix(),
|
|
421
|
+
executable: true,
|
|
422
|
+
},
|
|
423
|
+
];
|
|
424
|
+
const statuses = entries.map((entry) => statusForText(entry.path, entry.contents));
|
|
425
|
+
if (!args.dryRun) {
|
|
426
|
+
mkdirSync(args.binDir, { recursive: true });
|
|
427
|
+
for (const entry of entries) {
|
|
428
|
+
writeFileAtomic(entry.path, entry.contents);
|
|
429
|
+
if (entry.executable && platform !== 'win32') {
|
|
430
|
+
try {
|
|
431
|
+
chmodSync(entry.path, 0o755);
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
// Ignore best-effort cleanup failures.
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return { binDir: args.binDir, binStatus: combineInstallStatus(statuses) };
|
|
440
|
+
}
|
|
441
|
+
function removeFileIfExists(path, dryRun) {
|
|
442
|
+
if (!existsSync(path))
|
|
443
|
+
return 'missing';
|
|
444
|
+
if (!dryRun)
|
|
445
|
+
rmSync(path, { force: true });
|
|
446
|
+
return 'removed';
|
|
447
|
+
}
|
|
448
|
+
export function uninstallWavexzoreSandboxBinShims(args) {
|
|
449
|
+
if (args.keepBin)
|
|
450
|
+
return { binDir: args.binDir, binStatus: 'kept' };
|
|
451
|
+
const paths = resolveWavexzoreSandboxBinShimPaths(args.binDir);
|
|
452
|
+
const helperPaths = [
|
|
453
|
+
resolveOpenCodeHelperMjsPath(args.binDir),
|
|
454
|
+
resolveOpenCodeHelperPath(args.binDir, 'linux'),
|
|
455
|
+
resolveOpenCodeHelperPath(args.binDir, 'win32'),
|
|
456
|
+
];
|
|
457
|
+
const statuses = [paths.js, paths.unix, paths.cmd, ...helperPaths].map((path) => removeFileIfExists(path, args.dryRun));
|
|
458
|
+
return { binDir: args.binDir, binStatus: combineUninstallStatus(statuses) };
|
|
459
|
+
}
|
|
460
|
+
export function removePathIfExists(path, dryRun) {
|
|
461
|
+
if (!existsSync(path))
|
|
462
|
+
return 'missing';
|
|
463
|
+
if (!dryRun)
|
|
464
|
+
rmSync(path, { recursive: true, force: true });
|
|
465
|
+
return 'removed';
|
|
466
|
+
}
|
|
467
|
+
export function removeEmptyDirectory(path, dryRun) {
|
|
468
|
+
try {
|
|
469
|
+
if (!existsSync(path) || readdirSync(path).length > 0)
|
|
470
|
+
return;
|
|
471
|
+
if (!dryRun)
|
|
472
|
+
rmSync(path);
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
// Ignore best-effort empty directory cleanup failures.
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const PATH_MARKER_START = '# >>> wavexzore-sandbox >>>';
|
|
479
|
+
const PATH_MARKER_END = '# <<< wavexzore-sandbox <<<';
|
|
480
|
+
function pathMarkerBlock(binDir, profilePath) {
|
|
481
|
+
const usesFish = profilePath.replace(/\\/g, '/').endsWith('/config.fish');
|
|
482
|
+
const body = usesFish ? `fish_add_path ${shellQuote(binDir)}` : `export PATH=${shellQuote(binDir)}:"$PATH"`;
|
|
483
|
+
return `${PATH_MARKER_START}\n${body}\n${PATH_MARKER_END}\n`;
|
|
484
|
+
}
|
|
485
|
+
function replacePathMarker(raw, replacement) {
|
|
486
|
+
const pattern = /(?:^|\r?\n)# >>> wavexzore-sandbox >>>\r?\n[\s\S]*?# <<< wavexzore-sandbox <<<\r?\n?/m;
|
|
487
|
+
const match = raw.match(pattern);
|
|
488
|
+
if (!match) {
|
|
489
|
+
const prefix = raw.length > 0 && !raw.endsWith('\n') ? '\n' : '';
|
|
490
|
+
return { text: `${raw}${prefix}${replacement}`, changed: true };
|
|
491
|
+
}
|
|
492
|
+
const existing = match[0].replace(/^\r?\n/, '');
|
|
493
|
+
if (existing === replacement)
|
|
494
|
+
return { text: raw, changed: false };
|
|
495
|
+
const leadingNewline = match[0].startsWith('\n') ? '\n' : '';
|
|
496
|
+
return { text: raw.replace(pattern, `${leadingNewline}${replacement}`), changed: true };
|
|
497
|
+
}
|
|
498
|
+
function removePathMarker(raw) {
|
|
499
|
+
const pattern = /(?:^|\r?\n)# >>> wavexzore-sandbox >>>\r?\n[\s\S]*?# <<< wavexzore-sandbox <<<\r?\n?/m;
|
|
500
|
+
const next = raw.replace(pattern, (match) => (match.startsWith('\n') ? '\n' : ''));
|
|
501
|
+
return { text: next.replace(/\n{3,}/g, '\n\n'), changed: next !== raw };
|
|
502
|
+
}
|
|
503
|
+
function updateWindowsUserPath(args) {
|
|
504
|
+
const current = args.envPath ?? runtimeEnv(args.env).Path ?? runtimeEnv(args.env).PATH ?? '';
|
|
505
|
+
const parts = current.split(';').filter((item) => item.trim().length > 0);
|
|
506
|
+
const normalizedBin = resolve(args.binDir)
|
|
507
|
+
.replace(/[\\/]+$/, '')
|
|
508
|
+
.toLowerCase();
|
|
509
|
+
const hasBin = parts.some((item) => resolve(item.trim())
|
|
510
|
+
.replace(/[\\/]+$/, '')
|
|
511
|
+
.toLowerCase() === normalizedBin);
|
|
512
|
+
if (args.remove) {
|
|
513
|
+
if (!hasBin)
|
|
514
|
+
return 'unchanged';
|
|
515
|
+
if (args.dryRun)
|
|
516
|
+
return 'removed';
|
|
517
|
+
const next = parts
|
|
518
|
+
.filter((item) => resolve(item.trim())
|
|
519
|
+
.replace(/[\\/]+$/, '')
|
|
520
|
+
.toLowerCase() !== normalizedBin)
|
|
521
|
+
.join(';');
|
|
522
|
+
const command = `[Environment]::SetEnvironmentVariable('Path', ${powershellSingleQuote(next)}, 'User')`;
|
|
523
|
+
const result = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command], {
|
|
524
|
+
encoding: 'utf8',
|
|
525
|
+
});
|
|
526
|
+
if (result.error || result.status !== 0)
|
|
527
|
+
throw new Error(result.error?.message || result.stderr || 'Failed to update Windows user Path');
|
|
528
|
+
return 'removed';
|
|
529
|
+
}
|
|
530
|
+
if (hasBin)
|
|
531
|
+
return 'present';
|
|
532
|
+
if (args.dryRun)
|
|
533
|
+
return 'updated';
|
|
534
|
+
const next = [...parts, args.binDir].join(';');
|
|
535
|
+
const command = `[Environment]::SetEnvironmentVariable('Path', ${powershellSingleQuote(next)}, 'User')`;
|
|
536
|
+
const result = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command], {
|
|
537
|
+
encoding: 'utf8',
|
|
538
|
+
});
|
|
539
|
+
if (result.error || result.status !== 0)
|
|
540
|
+
throw new Error(result.error?.message || result.stderr || 'Failed to update Windows user Path');
|
|
541
|
+
return 'updated';
|
|
542
|
+
}
|
|
543
|
+
export function setupWavexzoreSandboxPath(options = {}) {
|
|
544
|
+
const homeDir = resolveWavexzoreSandboxHomeDir(options);
|
|
545
|
+
const binDir = resolveWavexzoreSandboxBinDir({ ...options, homeDir });
|
|
546
|
+
const platform = runtimePlatform(options.platform);
|
|
547
|
+
const pathHint = pathHintForPlatform(binDir, platform);
|
|
548
|
+
if (isDirectoryOnPath(binDir, options.envPath, platform))
|
|
549
|
+
return { homeDir, binDir, pathStatus: 'present', pathHint };
|
|
550
|
+
if (platform === 'win32')
|
|
551
|
+
return {
|
|
552
|
+
homeDir,
|
|
553
|
+
binDir,
|
|
554
|
+
pathStatus: updateWindowsUserPath({ binDir, dryRun: options.dryRun, envPath: options.envPath, env: options.env }),
|
|
555
|
+
pathHint,
|
|
556
|
+
};
|
|
557
|
+
const pathConfigPath = shellProfilePath({
|
|
558
|
+
homeDir: defaultHome(options.env),
|
|
559
|
+
pathConfigPath: options.pathConfigPath,
|
|
560
|
+
shell: options.shell,
|
|
561
|
+
env: options.env,
|
|
562
|
+
});
|
|
563
|
+
const current = existsSync(pathConfigPath) ? readFileSync(pathConfigPath, 'utf8') : '';
|
|
564
|
+
const next = replacePathMarker(current, pathMarkerBlock(binDir, pathConfigPath));
|
|
565
|
+
if (!next.changed)
|
|
566
|
+
return { homeDir, binDir, pathStatus: 'present', pathConfigPath, pathHint };
|
|
567
|
+
writeFileAtomic(pathConfigPath, next.text, options.dryRun);
|
|
568
|
+
return { homeDir, binDir, pathStatus: 'updated', pathConfigPath, pathHint };
|
|
569
|
+
}
|
|
570
|
+
export function removeWavexzoreSandboxPath(options = {}) {
|
|
571
|
+
const homeDir = resolveWavexzoreSandboxHomeDir(options);
|
|
572
|
+
const binDir = resolveWavexzoreSandboxBinDir({ ...options, homeDir });
|
|
573
|
+
const platform = runtimePlatform(options.platform);
|
|
574
|
+
const pathHint = pathHintForPlatform(binDir, platform);
|
|
575
|
+
if (platform === 'win32')
|
|
576
|
+
return {
|
|
577
|
+
homeDir,
|
|
578
|
+
binDir,
|
|
579
|
+
pathStatus: updateWindowsUserPath({
|
|
580
|
+
binDir,
|
|
581
|
+
remove: true,
|
|
582
|
+
dryRun: options.dryRun,
|
|
583
|
+
envPath: options.envPath,
|
|
584
|
+
env: options.env,
|
|
585
|
+
}),
|
|
586
|
+
pathHint,
|
|
587
|
+
};
|
|
588
|
+
const pathConfigPath = shellProfilePath({
|
|
589
|
+
homeDir: defaultHome(options.env),
|
|
590
|
+
pathConfigPath: options.pathConfigPath,
|
|
591
|
+
shell: options.shell,
|
|
592
|
+
env: options.env,
|
|
593
|
+
});
|
|
594
|
+
if (!existsSync(pathConfigPath))
|
|
595
|
+
return { homeDir, binDir, pathStatus: 'unchanged', pathConfigPath, pathHint };
|
|
596
|
+
const next = removePathMarker(readFileSync(pathConfigPath, 'utf8'));
|
|
597
|
+
if (!next.changed)
|
|
598
|
+
return { homeDir, binDir, pathStatus: 'unchanged', pathConfigPath, pathHint };
|
|
599
|
+
writeFileAtomic(pathConfigPath, next.text, options.dryRun);
|
|
600
|
+
return { homeDir, binDir, pathStatus: 'removed', pathConfigPath, pathHint };
|
|
601
|
+
}
|
|
602
|
+
export function relativeWithin(root, target) {
|
|
603
|
+
return relative(root, target).replace(/\\/g, '/');
|
|
604
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type HostConfigResult, type HostConfigUninstallResult, type InstallStatus } from './types.js';
|
|
2
|
+
type OpenCodeConfig = Record<string, unknown> & {
|
|
3
|
+
plugin?: unknown;
|
|
4
|
+
$schema?: unknown;
|
|
5
|
+
};
|
|
6
|
+
export declare function readOpenCodeConfig(configPath: string): OpenCodeConfig;
|
|
7
|
+
export declare function normalizePluginEntries(value: unknown): unknown[];
|
|
8
|
+
export declare function isKnownWavexzoreSandboxPluginEntry(spec: string): boolean;
|
|
9
|
+
export declare function nextPluginEntries(current: unknown[], pluginEntry: string): {
|
|
10
|
+
plugins: unknown[];
|
|
11
|
+
removed: string[];
|
|
12
|
+
pluginStatus: InstallStatus;
|
|
13
|
+
};
|
|
14
|
+
export declare function installOpenCodeConfig(args: {
|
|
15
|
+
configPath: string;
|
|
16
|
+
pluginEntry: string;
|
|
17
|
+
dryRun?: boolean;
|
|
18
|
+
}): HostConfigResult;
|
|
19
|
+
export declare function uninstallOpenCodeConfig(args: {
|
|
20
|
+
configPath: string;
|
|
21
|
+
pluginEntry: string;
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
keepConfig?: boolean;
|
|
24
|
+
}): HostConfigUninstallResult;
|
|
25
|
+
export {};
|