@open-audio-stack/core 0.1.44 → 0.1.46
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { Package } from './Package.js';
|
|
3
3
|
import { Manager } from './Manager.js';
|
|
4
|
-
import { archiveExtract, dirCreate, dirDelete, dirEmpty, dirMove, dirRead, fileCreate, fileCreateJson, fileCreateYaml, fileExists, fileHash, fileInstall, fileOpen, fileReadJson, fileReadYaml, filesMove, isAdmin, runCliAsAdmin, } from '../helpers/file.js';
|
|
4
|
+
import { archiveExtract, dirCreate, dirDelete, dirEmpty, dirIs, dirMove, dirRead, fileCreate, fileCreateJson, fileCreateYaml, fileExists, fileHash, fileInstall, fileOpen, fileReadJson, fileReadYaml, filesMove, isAdmin, runCliAsAdmin, } from '../helpers/file.js';
|
|
5
5
|
import { isValidVersion, pathGetSlug, pathGetVersion, toSlug } from '../helpers/utils.js';
|
|
6
6
|
import { commandExists, getArchitecture, getSystem, isTests } from '../helpers/utilsLocal.js';
|
|
7
7
|
import { apiBuffer } from '../helpers/api.js';
|
|
@@ -260,12 +260,43 @@ export class ManagerLocal extends Manager {
|
|
|
260
260
|
fileCreateJson(path.join(dirTarget, 'index.json'), pkgVersion);
|
|
261
261
|
}
|
|
262
262
|
else {
|
|
263
|
-
//
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
263
|
+
// Check if archive contains installer files (pkg, dmg) that should be run
|
|
264
|
+
const allFiles = dirRead(`${dirSource}/**/*`).filter(f => !dirIs(f));
|
|
265
|
+
const installerFiles = allFiles.filter(f => {
|
|
266
|
+
const ext = path.extname(f).toLowerCase();
|
|
267
|
+
return ext === '.pkg' || ext === '.dmg';
|
|
268
268
|
});
|
|
269
|
+
if (installerFiles.length > 0) {
|
|
270
|
+
// Run installer files found in archive
|
|
271
|
+
for (const installerFile of installerFiles) {
|
|
272
|
+
if (isTests())
|
|
273
|
+
fileOpen(installerFile);
|
|
274
|
+
else
|
|
275
|
+
fileInstall(installerFile);
|
|
276
|
+
}
|
|
277
|
+
// Create directory and save package info for installer
|
|
278
|
+
const dirTarget = path.join(this.typeDir, 'Installers', dirSub);
|
|
279
|
+
dirCreate(dirTarget);
|
|
280
|
+
fileCreateJson(path.join(dirTarget, 'index.json'), pkgVersion);
|
|
281
|
+
}
|
|
282
|
+
else if (this.type === RegistryType.Plugins) {
|
|
283
|
+
// For plugins, move files into type-specific subdirectories
|
|
284
|
+
const filesMoved = filesMove(dirSource, this.typeDir, dirSub, formatDir);
|
|
285
|
+
if (filesMoved.length === 0) {
|
|
286
|
+
throw new Error(`No compatible files found to install for ${slug}`);
|
|
287
|
+
}
|
|
288
|
+
filesMoved.forEach((fileMoved) => {
|
|
289
|
+
const fileJson = path.join(path.dirname(fileMoved), 'index.json');
|
|
290
|
+
fileCreateJson(fileJson, pkgVersion);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
// For apps/projects/presets, move entire directory without type subdirectories
|
|
295
|
+
const dirTarget = path.join(this.typeDir, dirSub);
|
|
296
|
+
dirCreate(dirTarget);
|
|
297
|
+
dirMove(dirSource, dirTarget);
|
|
298
|
+
fileCreateJson(path.join(dirTarget, 'index.json'), pkgVersion);
|
|
299
|
+
}
|
|
269
300
|
}
|
|
270
301
|
}
|
|
271
302
|
}
|
|
@@ -282,9 +313,9 @@ export class ManagerLocal extends Manager {
|
|
|
282
313
|
return this.listPackages();
|
|
283
314
|
}
|
|
284
315
|
// Loop through all packages and install each one.
|
|
285
|
-
for (const
|
|
316
|
+
for (const pkg of this.listPackages()) {
|
|
286
317
|
const versionNum = pkg.latestVersion();
|
|
287
|
-
await this.install(slug, versionNum);
|
|
318
|
+
await this.install(pkg.slug, versionNum);
|
|
288
319
|
}
|
|
289
320
|
return this.listPackages();
|
|
290
321
|
}
|
|
@@ -295,15 +326,17 @@ export class ManagerLocal extends Manager {
|
|
|
295
326
|
manager.scan();
|
|
296
327
|
const pkg = manager.getPackage(slug);
|
|
297
328
|
if (!pkg)
|
|
298
|
-
|
|
329
|
+
throw new Error(`Package ${slug} not found in registry`);
|
|
299
330
|
const versionNum = version || pkg.latestVersion();
|
|
300
331
|
const pkgVersion = pkg?.getVersion(versionNum);
|
|
301
332
|
if (!pkgVersion)
|
|
302
|
-
|
|
333
|
+
throw new Error(`Package ${slug} version ${versionNum} not found in registry`);
|
|
303
334
|
// Get local package file.
|
|
304
335
|
const pkgFile = packageLoadFile(filePath);
|
|
305
336
|
if (pkgFile[type] && pkgFile[type][slug] && pkgFile[type][slug] === versionNum) {
|
|
306
|
-
|
|
337
|
+
this.log(`Package ${slug} version ${versionNum} is already a dependency`);
|
|
338
|
+
pkgFile.installed = true;
|
|
339
|
+
return pkgFile;
|
|
307
340
|
}
|
|
308
341
|
// Install dependency.
|
|
309
342
|
await manager.install(slug, version);
|
|
@@ -434,9 +467,9 @@ export class ManagerLocal extends Manager {
|
|
|
434
467
|
// Get local package file.
|
|
435
468
|
const pkgFile = packageLoadFile(filePath);
|
|
436
469
|
if (!pkgFile[type])
|
|
437
|
-
|
|
470
|
+
throw new Error(`Package ${type} is missing`);
|
|
438
471
|
if (!pkgFile[type][slug])
|
|
439
|
-
|
|
472
|
+
throw new Error(`Package ${type} ${slug} is not a dependency`);
|
|
440
473
|
// Uninstall dependency.
|
|
441
474
|
const manager = new ManagerLocal(type, this.config.config);
|
|
442
475
|
await manager.sync();
|
package/build/classes/Package.js
CHANGED
|
@@ -28,7 +28,7 @@ export class Package extends Base {
|
|
|
28
28
|
if (Object.keys(report).length > 0)
|
|
29
29
|
this.reports.set(num, report);
|
|
30
30
|
if (errors.length > 0)
|
|
31
|
-
|
|
31
|
+
throw new Error(`Package ${version.name} version ${num} has validation errors: ${JSON.stringify(errors)}`);
|
|
32
32
|
version.verified = packageIsVerified(this.slug, version);
|
|
33
33
|
this.versions.set(num, version);
|
|
34
34
|
this.version = this.latestVersion();
|
package/build/helpers/admin.js
CHANGED
|
@@ -35,22 +35,34 @@ export function adminArguments() {
|
|
|
35
35
|
return args;
|
|
36
36
|
}
|
|
37
37
|
export async function adminInit() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
try {
|
|
39
|
+
const args = adminArguments();
|
|
40
|
+
const manager = new ManagerLocal(args.type, { appDir: args.appDir });
|
|
41
|
+
if (args.log)
|
|
42
|
+
manager.logEnable();
|
|
43
|
+
manager.log('adminInit', args);
|
|
44
|
+
await manager.sync();
|
|
45
|
+
manager.scan();
|
|
46
|
+
if (args.operation === 'install') {
|
|
47
|
+
await manager.install(args.id, args.version);
|
|
48
|
+
}
|
|
49
|
+
else if (args.operation === 'uninstall') {
|
|
50
|
+
await manager.uninstall(args.id, args.version);
|
|
51
|
+
}
|
|
52
|
+
else if (args.operation === 'installAll') {
|
|
53
|
+
await manager.installAll();
|
|
54
|
+
}
|
|
55
|
+
const result = { status: 'ok', code: 0 };
|
|
56
|
+
process.stdout.write('\n');
|
|
57
|
+
console.log(JSON.stringify(result));
|
|
58
|
+
process.exit(0);
|
|
50
59
|
}
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
catch (err) {
|
|
61
|
+
const message = err && err.message ? err.message : String(err);
|
|
62
|
+
const errorResult = { status: 'error', code: err && err.code ? err.code : 1, message };
|
|
63
|
+
process.stdout.write('\n');
|
|
64
|
+
console.error(JSON.stringify(errorResult));
|
|
65
|
+
process.exit(typeof errorResult.code === 'number' ? errorResult.code : 1);
|
|
53
66
|
}
|
|
54
|
-
console.log('ADMIN_COMPLETE');
|
|
55
67
|
}
|
|
56
68
|
adminInit();
|
package/build/helpers/file.js
CHANGED
|
@@ -16,9 +16,14 @@ import { fileURLToPath } from 'url';
|
|
|
16
16
|
import sudoPrompt from '@vscode/sudo-prompt';
|
|
17
17
|
import { getSystem } from './utilsLocal.js';
|
|
18
18
|
import { log } from './utils.js';
|
|
19
|
+
import mime from 'mime-types';
|
|
19
20
|
export async function archiveExtract(filePath, dirPath) {
|
|
20
21
|
log('⎋', dirPath);
|
|
22
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
21
23
|
const ext = path.extname(filePath).trim().toLowerCase();
|
|
24
|
+
const tarExtensions = ['.tar', '.gz', '.tgz', '.xz', '.bz2', '.tbz2'];
|
|
25
|
+
const tarCompoundExtensions = ['.tar.gz', '.tar.xz', '.tar.bz2'];
|
|
26
|
+
const isTarFile = tarExtensions.includes(ext) || tarCompoundExtensions.some(compoundExt => fileName.endsWith(compoundExt));
|
|
22
27
|
if (ext === '.zip') {
|
|
23
28
|
const zip = new AdmZip(filePath);
|
|
24
29
|
try {
|
|
@@ -44,16 +49,19 @@ export async function archiveExtract(filePath, dirPath) {
|
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
|
-
else if (
|
|
52
|
+
else if (isTarFile) {
|
|
48
53
|
return await tar.extract({
|
|
49
54
|
file: filePath,
|
|
50
55
|
cwd: dirPath,
|
|
51
56
|
});
|
|
52
57
|
}
|
|
53
58
|
else if (ext === '.7z') {
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
unpack(filePath, dirPath, (err2) => {
|
|
61
|
+
if (err2)
|
|
62
|
+
return reject(new Error(`7z extraction failed: ${err2 && err2.message ? err2.message : String(err2)}`));
|
|
63
|
+
return resolve();
|
|
64
|
+
});
|
|
57
65
|
});
|
|
58
66
|
}
|
|
59
67
|
}
|
|
@@ -247,14 +255,51 @@ export function fileMove(filePath, newPath) {
|
|
|
247
255
|
return false;
|
|
248
256
|
}
|
|
249
257
|
export function filesMove(dirSource, dirTarget, dirSub, formatDir) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
258
|
+
const filesAndFolders = dirRead(`${dirSource}/**/*`);
|
|
259
|
+
log('filesAndFolders', filesAndFolders);
|
|
260
|
+
// First pass: identify bundle directories (app, clap, vst3, lv2, etc.)
|
|
261
|
+
const bundleDirs = new Set();
|
|
262
|
+
filesAndFolders.forEach(f => {
|
|
263
|
+
if (dirIs(f)) {
|
|
264
|
+
// Check if this is a macOS application bundle or plugin bundle
|
|
265
|
+
if (fileExists(path.join(f, 'Contents', 'Info.plist'))) {
|
|
266
|
+
bundleDirs.add(f);
|
|
267
|
+
}
|
|
268
|
+
// Check if this is an LV2 plugin folder
|
|
269
|
+
if (fileExists(path.join(f, 'manifest.ttl'))) {
|
|
270
|
+
bundleDirs.add(f);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
const files = filesAndFolders.filter(f => {
|
|
275
|
+
// Exclude files/folders that are inside bundle directories
|
|
276
|
+
for (const bundleDir of bundleDirs) {
|
|
277
|
+
if (f.startsWith(bundleDir + path.sep)) {
|
|
278
|
+
return false; // This path is inside a bundle, exclude it
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Include regular files (not directories).
|
|
282
|
+
if (!dirIs(f))
|
|
283
|
+
return true;
|
|
284
|
+
// Include bundle directories themselves (already identified above).
|
|
285
|
+
if (bundleDirs.has(f))
|
|
286
|
+
return true;
|
|
287
|
+
// Otherwise ignore.
|
|
288
|
+
return false;
|
|
289
|
+
});
|
|
253
290
|
const filesMoved = [];
|
|
291
|
+
log('files', files);
|
|
254
292
|
// For each file, move to correct folder based on type
|
|
255
293
|
files.forEach((fileSource) => {
|
|
256
294
|
const fileExt = path.extname(fileSource).slice(1).toLowerCase();
|
|
257
|
-
|
|
295
|
+
let fileExtTarget = formatDir[fileExt];
|
|
296
|
+
// Use mime-type detection as fallback for unmapped extensions
|
|
297
|
+
if (!fileExtTarget) {
|
|
298
|
+
const mimeType = mime.lookup(fileSource) || '';
|
|
299
|
+
if (!mimeType || mimeType.startsWith('application/')) {
|
|
300
|
+
fileExtTarget = 'App';
|
|
301
|
+
}
|
|
302
|
+
}
|
|
258
303
|
// If this is not a supported file format, then ignore.
|
|
259
304
|
if (fileExtTarget === undefined)
|
|
260
305
|
return log(`${fileSource} - ${fileExt || 'no extension'} not mapped to a installation folder, skipping.`);
|
|
@@ -373,28 +418,46 @@ export function runCliAsAdmin(args) {
|
|
|
373
418
|
const filename = fileURLToPath(import.meta.url).replace('src/', 'build/');
|
|
374
419
|
const dirPathClean = dirname(filename).replace('app.asar', 'app.asar.unpacked');
|
|
375
420
|
const script = path.join(dirPathClean, 'admin.js');
|
|
376
|
-
// Temp file for logging
|
|
377
|
-
const logFile = path.join(dirPathClean, 'admin.log');
|
|
378
|
-
writeFileSync(logFile, ''); // Clear previous logs
|
|
379
421
|
log(`Running as admin: node "${script}" ${args}`);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
422
|
+
const cmd = `node "${script}" ${args}`;
|
|
423
|
+
sudoPrompt.exec(cmd, { name: 'Open Audio Stack' }, (error, stdout, stderr) => {
|
|
424
|
+
// Convert stdout/stderr buffers to strings for inspection
|
|
425
|
+
const stdoutStr = stdout ? (typeof stdout === 'string' ? stdout : stdout.toString()) : '';
|
|
426
|
+
const stderrStr = stderr ? (typeof stderr === 'string' ? stderr : stderr.toString()) : '';
|
|
427
|
+
const out = stdoutStr + stderrStr;
|
|
428
|
+
log(out);
|
|
429
|
+
// Try to parse structured JSON output from the admin script first.
|
|
430
|
+
// Admin script outputs JSON on its own line after a newline, so look for the last JSON object.
|
|
431
|
+
const lines = out.split('\n');
|
|
432
|
+
let jsonPayload = null;
|
|
433
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
434
|
+
const line = lines[i].trim();
|
|
435
|
+
if (!line)
|
|
436
|
+
continue; // Skip empty lines
|
|
437
|
+
try {
|
|
438
|
+
jsonPayload = JSON.parse(line);
|
|
439
|
+
break; // Found valid JSON, stop searching backwards
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
// This line is not JSON, continue searching
|
|
443
|
+
}
|
|
390
444
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
445
|
+
// If we found JSON output from admin script, prioritize it over sudoPrompt error
|
|
446
|
+
if (jsonPayload) {
|
|
447
|
+
if (jsonPayload && (jsonPayload.status === 'ok' || jsonPayload.code === 0)) {
|
|
448
|
+
return resolve();
|
|
449
|
+
}
|
|
450
|
+
const errMsg = jsonPayload && jsonPayload.message ? jsonPayload.message : JSON.stringify(jsonPayload);
|
|
451
|
+
return reject(new Error(`runCliAsAdmin: admin command reported error: ${errMsg}`));
|
|
452
|
+
}
|
|
453
|
+
// If no JSON found, check for sudoPrompt error
|
|
454
|
+
if (error) {
|
|
455
|
+
const msg = `runCliAsAdmin: admin command failed: ${error && error.message ? error.message : String(error)}${stderrStr ? `\nstderr: ${stderrStr}` : ''}`;
|
|
456
|
+
const err = new Error(msg);
|
|
457
|
+
err.code = error && error.code ? error.code : undefined;
|
|
458
|
+
return reject(err);
|
|
397
459
|
}
|
|
460
|
+
return reject(new Error(`runCliAsAdmin: admin command did not report completion. stdout: ${stdoutStr} stderr: ${stderrStr}`));
|
|
398
461
|
});
|
|
399
462
|
});
|
|
400
463
|
}
|
|
@@ -19,9 +19,7 @@ export declare enum PluginFormat {
|
|
|
19
19
|
export type PluginFormatDir = {
|
|
20
20
|
[format in PluginFormat]: string;
|
|
21
21
|
};
|
|
22
|
-
export declare const pluginFormatDir: PluginFormatDir
|
|
23
|
-
'': string;
|
|
24
|
-
};
|
|
22
|
+
export declare const pluginFormatDir: PluginFormatDir;
|
|
25
23
|
export interface PluginFormatOption {
|
|
26
24
|
description: string;
|
|
27
25
|
value: PluginFormat;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-audio-stack/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.46",
|
|
4
4
|
"description": "Open-source audio plugin management software",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./build/index.js",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"@types/adm-zip": "^0.5.5",
|
|
43
43
|
"@types/fs-extra": "^11.0.4",
|
|
44
44
|
"@types/js-yaml": "^4.0.9",
|
|
45
|
+
"@types/mime-types": "^3.0.1",
|
|
45
46
|
"@types/node": "^22.7.8",
|
|
46
47
|
"@types/semver": "^7.5.8",
|
|
47
48
|
"@vitest/coverage-v8": "^3.0.5",
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
"glob": "^11.0.0",
|
|
63
64
|
"inquirer": "^12.4.1",
|
|
64
65
|
"js-yaml": "^4.1.0",
|
|
66
|
+
"mime-types": "^3.0.2",
|
|
65
67
|
"semver": "^7.6.3",
|
|
66
68
|
"slugify": "^1.6.6",
|
|
67
69
|
"tar": "^7.4.3",
|