@open-audio-stack/core 0.1.45 → 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,14 +35,14 @@ export function adminArguments() {
|
|
|
35
35
|
return args;
|
|
36
36
|
}
|
|
37
37
|
export async function adminInit() {
|
|
38
|
-
const args = adminArguments();
|
|
39
|
-
const manager = new ManagerLocal(args.type, { appDir: args.appDir });
|
|
40
|
-
if (args.log)
|
|
41
|
-
manager.logEnable();
|
|
42
|
-
manager.log('adminInit', args);
|
|
43
|
-
await manager.sync();
|
|
44
|
-
manager.scan();
|
|
45
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
46
|
if (args.operation === 'install') {
|
|
47
47
|
await manager.install(args.id, args.version);
|
|
48
48
|
}
|
|
@@ -61,7 +61,7 @@ export async function adminInit() {
|
|
|
61
61
|
const message = err && err.message ? err.message : String(err);
|
|
62
62
|
const errorResult = { status: 'error', code: err && err.code ? err.code : 1, message };
|
|
63
63
|
process.stdout.write('\n');
|
|
64
|
-
console.
|
|
64
|
+
console.error(JSON.stringify(errorResult));
|
|
65
65
|
process.exit(typeof errorResult.code === 'number' ? errorResult.code : 1);
|
|
66
66
|
}
|
|
67
67
|
}
|
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,7 +49,7 @@ 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,
|
|
@@ -252,15 +257,32 @@ export function fileMove(filePath, newPath) {
|
|
|
252
257
|
export function filesMove(dirSource, dirTarget, dirSub, formatDir) {
|
|
253
258
|
const filesAndFolders = dirRead(`${dirSource}/**/*`);
|
|
254
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
|
+
});
|
|
255
274
|
const files = filesAndFolders.filter(f => {
|
|
256
|
-
//
|
|
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).
|
|
257
282
|
if (!dirIs(f))
|
|
258
283
|
return true;
|
|
259
|
-
// Include
|
|
260
|
-
if (
|
|
261
|
-
return true;
|
|
262
|
-
// Include LV2 plugin folders.
|
|
263
|
-
if (fileExists(path.join(f, 'manifest.ttl')))
|
|
284
|
+
// Include bundle directories themselves (already identified above).
|
|
285
|
+
if (bundleDirs.has(f))
|
|
264
286
|
return true;
|
|
265
287
|
// Otherwise ignore.
|
|
266
288
|
return false;
|
|
@@ -270,7 +292,14 @@ export function filesMove(dirSource, dirTarget, dirSub, formatDir) {
|
|
|
270
292
|
// For each file, move to correct folder based on type
|
|
271
293
|
files.forEach((fileSource) => {
|
|
272
294
|
const fileExt = path.extname(fileSource).slice(1).toLowerCase();
|
|
273
|
-
|
|
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
|
+
}
|
|
274
303
|
// If this is not a supported file format, then ignore.
|
|
275
304
|
if (fileExtTarget === undefined)
|
|
276
305
|
return log(`${fileSource} - ${fileExt || 'no extension'} not mapped to a installation folder, skipping.`);
|
|
@@ -392,14 +421,6 @@ export function runCliAsAdmin(args) {
|
|
|
392
421
|
log(`Running as admin: node "${script}" ${args}`);
|
|
393
422
|
const cmd = `node "${script}" ${args}`;
|
|
394
423
|
sudoPrompt.exec(cmd, { name: 'Open Audio Stack' }, (error, stdout, stderr) => {
|
|
395
|
-
// Prefer explicit error from sudo-prompt callback
|
|
396
|
-
if (error) {
|
|
397
|
-
const stderrStr = stderr ? (typeof stderr === 'string' ? stderr : stderr.toString()) : '';
|
|
398
|
-
const msg = `runCliAsAdmin: admin command failed: ${error && error.message ? error.message : String(error)}${stderrStr ? `\nstderr: ${stderrStr}` : ''}`;
|
|
399
|
-
const err = new Error(msg);
|
|
400
|
-
err.code = error && error.code ? error.code : undefined;
|
|
401
|
-
return reject(err);
|
|
402
|
-
}
|
|
403
424
|
// Convert stdout/stderr buffers to strings for inspection
|
|
404
425
|
const stdoutStr = stdout ? (typeof stdout === 'string' ? stdout : stdout.toString()) : '';
|
|
405
426
|
const stderrStr = stderr ? (typeof stderr === 'string' ? stderr : stderr.toString()) : '';
|
|
@@ -421,6 +442,7 @@ export function runCliAsAdmin(args) {
|
|
|
421
442
|
// This line is not JSON, continue searching
|
|
422
443
|
}
|
|
423
444
|
}
|
|
445
|
+
// If we found JSON output from admin script, prioritize it over sudoPrompt error
|
|
424
446
|
if (jsonPayload) {
|
|
425
447
|
if (jsonPayload && (jsonPayload.status === 'ok' || jsonPayload.code === 0)) {
|
|
426
448
|
return resolve();
|
|
@@ -428,6 +450,13 @@ export function runCliAsAdmin(args) {
|
|
|
428
450
|
const errMsg = jsonPayload && jsonPayload.message ? jsonPayload.message : JSON.stringify(jsonPayload);
|
|
429
451
|
return reject(new Error(`runCliAsAdmin: admin command reported error: ${errMsg}`));
|
|
430
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);
|
|
459
|
+
}
|
|
431
460
|
return reject(new Error(`runCliAsAdmin: admin command did not report completion. stdout: ${stdoutStr} stderr: ${stderrStr}`));
|
|
432
461
|
});
|
|
433
462
|
});
|
|
@@ -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",
|