busy-cli 0.1.3 → 0.2.1
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/index.js +4 -3
- package/dist/commands/package.d.ts +5 -2
- package/dist/commands/package.d.ts.map +1 -1
- package/dist/commands/package.js +31 -3
- package/dist/package/manifest.d.ts +12 -0
- package/dist/package/manifest.d.ts.map +1 -1
- package/dist/package/manifest.js +118 -0
- package/dist/parsers/operations.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/operations.test.ts +1 -1
- package/src/cli/index.ts +4 -3
- package/src/commands/package.ts +32 -3
- package/src/package/manifest.ts +138 -0
- package/src/parsers/operations.ts +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -310,14 +310,15 @@ const packageCmd = program
|
|
|
310
310
|
// Package add
|
|
311
311
|
packageCmd
|
|
312
312
|
.command('add')
|
|
313
|
-
.description('Add a package from URL')
|
|
314
|
-
.argument('<url>', 'URL to the BUSY document')
|
|
313
|
+
.description('Add a package from URL or local folder')
|
|
314
|
+
.argument('<url>', 'URL or local path to the BUSY document or folder')
|
|
315
315
|
.option('-d, --dir <directory>', 'Workspace directory', '.')
|
|
316
|
+
.option('-r, --recursive', 'Recursively add all files from a local folder')
|
|
316
317
|
.action(async (url, options) => {
|
|
317
318
|
try {
|
|
318
319
|
const workspaceRoot = resolve(options.dir);
|
|
319
320
|
console.log(`\nAdding package from: ${url}\n`);
|
|
320
|
-
const result = await addPackage(workspaceRoot, url);
|
|
321
|
+
const result = await addPackage(workspaceRoot, url, { recursive: options.recursive });
|
|
321
322
|
console.log(` ID: ${result.id}`);
|
|
322
323
|
console.log(` Provider: ${result.provider}`);
|
|
323
324
|
console.log(` Version: ${result.version}`);
|
|
@@ -71,12 +71,15 @@ export declare function checkWorkspace(workspaceRoot: string, options?: {
|
|
|
71
71
|
skipExternal?: boolean;
|
|
72
72
|
}): Promise<CheckResult>;
|
|
73
73
|
/**
|
|
74
|
-
* Add a package from URL
|
|
74
|
+
* Add a package from URL or local folder
|
|
75
75
|
*
|
|
76
76
|
* If the URL points to a package.busy.md manifest, fetches the entire package.
|
|
77
|
+
* If it's a local directory with --recursive (or without a manifest), copies all files.
|
|
77
78
|
* Otherwise, fetches a single file.
|
|
78
79
|
*/
|
|
79
|
-
export declare function addPackage(workspaceRoot: string, url: string
|
|
80
|
+
export declare function addPackage(workspaceRoot: string, url: string, options?: {
|
|
81
|
+
recursive?: boolean;
|
|
82
|
+
}): Promise<AddResult>;
|
|
80
83
|
/**
|
|
81
84
|
* Remove a package
|
|
82
85
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../src/commands/package.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAmB,YAAY,EAAiC,MAAM,sBAAsB,CAAC;AAKpG,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,wBAAwB,CAAC;AAChC,OAAO,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAkC9E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CA8CtH;AAED
|
|
1
|
+
{"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../src/commands/package.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAmB,YAAY,EAAiC,MAAM,sBAAsB,CAAC;AAKpG,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,wBAAwB,CAAC;AAChC,OAAO,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAkC9E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CA8CtH;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAgH1H;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBnG;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAQ7E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAM3G;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA4ErG"}
|
package/dist/commands/package.js
CHANGED
|
@@ -8,7 +8,7 @@ import * as path from 'node:path';
|
|
|
8
8
|
import { CacheManager, deriveCachePath } from '../cache/index.js';
|
|
9
9
|
import { PackageRegistry, deriveEntryId, deriveCategory } from '../registry/index.js';
|
|
10
10
|
import { providerRegistry } from '../providers/index.js';
|
|
11
|
-
import { isPackageManifestUrl, fetchPackageFromManifest } from '../package/manifest.js';
|
|
11
|
+
import { isPackageManifestUrl, fetchPackageFromManifest, fetchPackageFromLocalFolder } from '../package/manifest.js';
|
|
12
12
|
// Ensure providers are registered
|
|
13
13
|
import '../providers/local.js';
|
|
14
14
|
import '../providers/github.js';
|
|
@@ -94,12 +94,40 @@ export async function checkWorkspace(workspaceRoot, options) {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
|
-
* Add a package from URL
|
|
97
|
+
* Add a package from URL or local folder
|
|
98
98
|
*
|
|
99
99
|
* If the URL points to a package.busy.md manifest, fetches the entire package.
|
|
100
|
+
* If it's a local directory with --recursive (or without a manifest), copies all files.
|
|
100
101
|
* Otherwise, fetches a single file.
|
|
101
102
|
*/
|
|
102
|
-
export async function addPackage(workspaceRoot, url) {
|
|
103
|
+
export async function addPackage(workspaceRoot, url, options) {
|
|
104
|
+
// Check if this is a local directory that should use folder-based discovery
|
|
105
|
+
if (url.startsWith('./') || url.startsWith('../') || url.startsWith('/') || (!url.includes('://') && !url.startsWith('http'))) {
|
|
106
|
+
const resolvedPath = path.isAbsolute(url) ? url : path.resolve(process.cwd(), url);
|
|
107
|
+
try {
|
|
108
|
+
const stat = await fs.stat(resolvedPath);
|
|
109
|
+
if (stat.isDirectory()) {
|
|
110
|
+
const manifestExists = await fs.stat(path.join(resolvedPath, 'package.busy.md'))
|
|
111
|
+
.then(() => true).catch(() => false);
|
|
112
|
+
if (options?.recursive || !manifestExists) {
|
|
113
|
+
// Use folder-based discovery: --recursive flag or no manifest available
|
|
114
|
+
const result = await fetchPackageFromLocalFolder(workspaceRoot, resolvedPath);
|
|
115
|
+
return {
|
|
116
|
+
id: result.name,
|
|
117
|
+
source: resolvedPath,
|
|
118
|
+
provider: 'local',
|
|
119
|
+
cached: result.cached,
|
|
120
|
+
version: result.version,
|
|
121
|
+
integrity: result.integrity,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// Has manifest and not --recursive: fall through to manifest-based flow
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Not a directory or doesn't exist - fall through to normal handling
|
|
129
|
+
}
|
|
130
|
+
}
|
|
103
131
|
// Check if this is a package manifest URL
|
|
104
132
|
if (isPackageManifestUrl(url)) {
|
|
105
133
|
// Use manifest-based package installation
|
|
@@ -56,4 +56,16 @@ export declare function parsePackageManifest(content: string): PackageManifest;
|
|
|
56
56
|
* Fetch a package from its manifest URL or local path
|
|
57
57
|
*/
|
|
58
58
|
export declare function fetchPackageFromManifest(workspaceRoot: string, manifestUrl: string): Promise<FetchPackageResult>;
|
|
59
|
+
/**
|
|
60
|
+
* Recursively discover all files in a directory.
|
|
61
|
+
* Skips hidden directories (e.g. .git, .libraries) and node_modules.
|
|
62
|
+
*/
|
|
63
|
+
export declare function discoverFiles(dirPath: string): Promise<string[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Fetch a package from a local folder, copying all files.
|
|
66
|
+
*
|
|
67
|
+
* If a package.busy.md exists, its metadata (name, version, description) is used.
|
|
68
|
+
* Otherwise, metadata is derived from the folder name.
|
|
69
|
+
*/
|
|
70
|
+
export declare function fetchPackageFromLocalFolder(workspaceRoot: string, folderPath: string): Promise<FetchPackageResult>;
|
|
59
71
|
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/package/manifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,wBAAwB,CAAC;AAChC,OAAO,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAazD;AAyBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAiHrE;AAkBD;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CA4G7B"}
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/package/manifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,wBAAwB,CAAC;AAChC,OAAO,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAazD;AAyBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAiHrE;AAkBD;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CA4G7B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAoBtE;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,kBAAkB,CAAC,CAqG7B"}
|
package/dist/package/manifest.js
CHANGED
|
@@ -262,4 +262,122 @@ export async function fetchPackageFromManifest(workspaceRoot, manifestUrl) {
|
|
|
262
262
|
integrity,
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Recursively discover all files in a directory.
|
|
267
|
+
* Skips hidden directories (e.g. .git, .libraries) and node_modules.
|
|
268
|
+
*/
|
|
269
|
+
export async function discoverFiles(dirPath) {
|
|
270
|
+
const results = [];
|
|
271
|
+
async function walk(dir) {
|
|
272
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
273
|
+
for (const entry of entries) {
|
|
274
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const fullPath = path.join(dir, entry.name);
|
|
278
|
+
if (entry.isDirectory()) {
|
|
279
|
+
await walk(fullPath);
|
|
280
|
+
}
|
|
281
|
+
else if (entry.isFile()) {
|
|
282
|
+
results.push(fullPath);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
await walk(dirPath);
|
|
287
|
+
return results;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Fetch a package from a local folder, copying all files.
|
|
291
|
+
*
|
|
292
|
+
* If a package.busy.md exists, its metadata (name, version, description) is used.
|
|
293
|
+
* Otherwise, metadata is derived from the folder name.
|
|
294
|
+
*/
|
|
295
|
+
export async function fetchPackageFromLocalFolder(workspaceRoot, folderPath) {
|
|
296
|
+
const absolutePath = path.isAbsolute(folderPath)
|
|
297
|
+
? folderPath
|
|
298
|
+
: path.resolve(process.cwd(), folderPath);
|
|
299
|
+
// Verify it's a directory
|
|
300
|
+
const stat = await fs.stat(absolutePath);
|
|
301
|
+
if (!stat.isDirectory()) {
|
|
302
|
+
throw new Error(`Not a directory: ${absolutePath}`);
|
|
303
|
+
}
|
|
304
|
+
// Try to read manifest for metadata
|
|
305
|
+
const manifestPath = path.join(absolutePath, 'package.busy.md');
|
|
306
|
+
let manifest = null;
|
|
307
|
+
let manifestContent = null;
|
|
308
|
+
try {
|
|
309
|
+
manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
310
|
+
manifest = parsePackageManifest(manifestContent);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
// No manifest - that's fine
|
|
314
|
+
}
|
|
315
|
+
const packageName = manifest?.name || path.basename(absolutePath);
|
|
316
|
+
// Discover all files in the folder
|
|
317
|
+
const discoveredFiles = await discoverFiles(absolutePath);
|
|
318
|
+
// Filter out package.busy.md itself (handled separately)
|
|
319
|
+
const filesToCopy = discoveredFiles
|
|
320
|
+
.filter(f => path.basename(f) !== 'package.busy.md')
|
|
321
|
+
.map(f => ({
|
|
322
|
+
relativePath: path.relative(absolutePath, f),
|
|
323
|
+
absolutePath: f,
|
|
324
|
+
}));
|
|
325
|
+
// Initialize cache
|
|
326
|
+
const cache = new CacheManager(workspaceRoot);
|
|
327
|
+
await cache.init();
|
|
328
|
+
// Copy files
|
|
329
|
+
let combinedContent = '';
|
|
330
|
+
const documents = [];
|
|
331
|
+
for (const file of filesToCopy) {
|
|
332
|
+
try {
|
|
333
|
+
const content = await fs.readFile(file.absolutePath, 'utf-8');
|
|
334
|
+
combinedContent += content;
|
|
335
|
+
const cachePath = path.join(packageName, file.relativePath);
|
|
336
|
+
await cache.save(cachePath, content);
|
|
337
|
+
documents.push({
|
|
338
|
+
name: path.basename(file.relativePath, path.extname(file.relativePath)),
|
|
339
|
+
relativePath: './' + file.relativePath,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
console.warn(`Warning: Failed to read ${file.absolutePath}: ${error}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Save manifest if it exists
|
|
347
|
+
if (manifestContent) {
|
|
348
|
+
await cache.save(path.join(packageName, 'package.busy.md'), manifestContent);
|
|
349
|
+
}
|
|
350
|
+
// Calculate integrity
|
|
351
|
+
const integrity = calculateIntegrity(combinedContent);
|
|
352
|
+
// Add to registry
|
|
353
|
+
const registry = new PackageRegistry(workspaceRoot);
|
|
354
|
+
try {
|
|
355
|
+
await registry.load();
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
await registry.init();
|
|
359
|
+
await registry.load();
|
|
360
|
+
}
|
|
361
|
+
const entry = {
|
|
362
|
+
id: packageName,
|
|
363
|
+
description: manifest?.description || '',
|
|
364
|
+
source: absolutePath,
|
|
365
|
+
provider: 'local',
|
|
366
|
+
cached: `.libraries/${packageName}`,
|
|
367
|
+
version: manifest?.version || 'latest',
|
|
368
|
+
fetched: new Date().toISOString(),
|
|
369
|
+
integrity,
|
|
370
|
+
category: 'Packages',
|
|
371
|
+
};
|
|
372
|
+
registry.addPackage(entry);
|
|
373
|
+
await registry.save();
|
|
374
|
+
return {
|
|
375
|
+
name: packageName,
|
|
376
|
+
version: manifest?.version || 'latest',
|
|
377
|
+
description: manifest?.description || '',
|
|
378
|
+
documents,
|
|
379
|
+
cached: `.libraries/${packageName}`,
|
|
380
|
+
integrity,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
265
383
|
//# sourceMappingURL=manifest.js.map
|
|
@@ -230,7 +230,7 @@ function createOperation(section, docId, filePath) {
|
|
|
230
230
|
const id = `${docId}::${slug}`; // Use :: for concept IDs
|
|
231
231
|
// Parse steps and checklist from content
|
|
232
232
|
const { steps, checklist } = parseOperationContent(section);
|
|
233
|
-
// Get extends from section heading (e.g., ## [ValidateInput][
|
|
233
|
+
// Get extends from section heading (e.g., ## [ValidateInput][SomeType])
|
|
234
234
|
const extends_ = getSectionExtends(section.id);
|
|
235
235
|
return {
|
|
236
236
|
kind: 'operation',
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -352,16 +352,17 @@ const packageCmd = program
|
|
|
352
352
|
// Package add
|
|
353
353
|
packageCmd
|
|
354
354
|
.command('add')
|
|
355
|
-
.description('Add a package from URL')
|
|
356
|
-
.argument('<url>', 'URL to the BUSY document')
|
|
355
|
+
.description('Add a package from URL or local folder')
|
|
356
|
+
.argument('<url>', 'URL or local path to the BUSY document or folder')
|
|
357
357
|
.option('-d, --dir <directory>', 'Workspace directory', '.')
|
|
358
|
+
.option('-r, --recursive', 'Recursively add all files from a local folder')
|
|
358
359
|
.action(async (url: string, options) => {
|
|
359
360
|
try {
|
|
360
361
|
const workspaceRoot = resolve(options.dir);
|
|
361
362
|
|
|
362
363
|
console.log(`\nAdding package from: ${url}\n`);
|
|
363
364
|
|
|
364
|
-
const result = await addPackage(workspaceRoot, url);
|
|
365
|
+
const result = await addPackage(workspaceRoot, url, { recursive: options.recursive });
|
|
365
366
|
|
|
366
367
|
console.log(` ID: ${result.id}`);
|
|
367
368
|
console.log(` Provider: ${result.provider}`);
|
package/src/commands/package.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as path from 'node:path';
|
|
|
9
9
|
import { CacheManager, calculateIntegrity, deriveCachePath } from '../cache/index.js';
|
|
10
10
|
import { PackageRegistry, PackageEntry, deriveEntryId, deriveCategory } from '../registry/index.js';
|
|
11
11
|
import { providerRegistry } from '../providers/index.js';
|
|
12
|
-
import { isPackageManifestUrl, fetchPackageFromManifest } from '../package/manifest.js';
|
|
12
|
+
import { isPackageManifestUrl, fetchPackageFromManifest, fetchPackageFromLocalFolder } from '../package/manifest.js';
|
|
13
13
|
|
|
14
14
|
// Ensure providers are registered
|
|
15
15
|
import '../providers/local.js';
|
|
@@ -166,12 +166,41 @@ export async function checkWorkspace(workspaceRoot: string, options?: { skipExte
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
* Add a package from URL
|
|
169
|
+
* Add a package from URL or local folder
|
|
170
170
|
*
|
|
171
171
|
* If the URL points to a package.busy.md manifest, fetches the entire package.
|
|
172
|
+
* If it's a local directory with --recursive (or without a manifest), copies all files.
|
|
172
173
|
* Otherwise, fetches a single file.
|
|
173
174
|
*/
|
|
174
|
-
export async function addPackage(workspaceRoot: string, url: string): Promise<AddResult> {
|
|
175
|
+
export async function addPackage(workspaceRoot: string, url: string, options?: { recursive?: boolean }): Promise<AddResult> {
|
|
176
|
+
// Check if this is a local directory that should use folder-based discovery
|
|
177
|
+
if (url.startsWith('./') || url.startsWith('../') || url.startsWith('/') || (!url.includes('://') && !url.startsWith('http'))) {
|
|
178
|
+
const resolvedPath = path.isAbsolute(url) ? url : path.resolve(process.cwd(), url);
|
|
179
|
+
try {
|
|
180
|
+
const stat = await fs.stat(resolvedPath);
|
|
181
|
+
if (stat.isDirectory()) {
|
|
182
|
+
const manifestExists = await fs.stat(path.join(resolvedPath, 'package.busy.md'))
|
|
183
|
+
.then(() => true).catch(() => false);
|
|
184
|
+
|
|
185
|
+
if (options?.recursive || !manifestExists) {
|
|
186
|
+
// Use folder-based discovery: --recursive flag or no manifest available
|
|
187
|
+
const result = await fetchPackageFromLocalFolder(workspaceRoot, resolvedPath);
|
|
188
|
+
return {
|
|
189
|
+
id: result.name,
|
|
190
|
+
source: resolvedPath,
|
|
191
|
+
provider: 'local',
|
|
192
|
+
cached: result.cached,
|
|
193
|
+
version: result.version,
|
|
194
|
+
integrity: result.integrity,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// Has manifest and not --recursive: fall through to manifest-based flow
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
// Not a directory or doesn't exist - fall through to normal handling
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
175
204
|
// Check if this is a package manifest URL
|
|
176
205
|
if (isPackageManifestUrl(url)) {
|
|
177
206
|
// Use manifest-based package installation
|
package/src/package/manifest.ts
CHANGED
|
@@ -347,3 +347,141 @@ export async function fetchPackageFromManifest(
|
|
|
347
347
|
integrity,
|
|
348
348
|
};
|
|
349
349
|
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Recursively discover all files in a directory.
|
|
353
|
+
* Skips hidden directories (e.g. .git, .libraries) and node_modules.
|
|
354
|
+
*/
|
|
355
|
+
export async function discoverFiles(dirPath: string): Promise<string[]> {
|
|
356
|
+
const results: string[] = [];
|
|
357
|
+
|
|
358
|
+
async function walk(dir: string) {
|
|
359
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
360
|
+
for (const entry of entries) {
|
|
361
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const fullPath = path.join(dir, entry.name);
|
|
365
|
+
if (entry.isDirectory()) {
|
|
366
|
+
await walk(fullPath);
|
|
367
|
+
} else if (entry.isFile()) {
|
|
368
|
+
results.push(fullPath);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
await walk(dirPath);
|
|
374
|
+
return results;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Fetch a package from a local folder, copying all files.
|
|
379
|
+
*
|
|
380
|
+
* If a package.busy.md exists, its metadata (name, version, description) is used.
|
|
381
|
+
* Otherwise, metadata is derived from the folder name.
|
|
382
|
+
*/
|
|
383
|
+
export async function fetchPackageFromLocalFolder(
|
|
384
|
+
workspaceRoot: string,
|
|
385
|
+
folderPath: string,
|
|
386
|
+
): Promise<FetchPackageResult> {
|
|
387
|
+
const absolutePath = path.isAbsolute(folderPath)
|
|
388
|
+
? folderPath
|
|
389
|
+
: path.resolve(process.cwd(), folderPath);
|
|
390
|
+
|
|
391
|
+
// Verify it's a directory
|
|
392
|
+
const stat = await fs.stat(absolutePath);
|
|
393
|
+
if (!stat.isDirectory()) {
|
|
394
|
+
throw new Error(`Not a directory: ${absolutePath}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Try to read manifest for metadata
|
|
398
|
+
const manifestPath = path.join(absolutePath, 'package.busy.md');
|
|
399
|
+
let manifest: PackageManifest | null = null;
|
|
400
|
+
let manifestContent: string | null = null;
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
404
|
+
manifest = parsePackageManifest(manifestContent);
|
|
405
|
+
} catch {
|
|
406
|
+
// No manifest - that's fine
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const packageName = manifest?.name || path.basename(absolutePath);
|
|
410
|
+
|
|
411
|
+
// Discover all files in the folder
|
|
412
|
+
const discoveredFiles = await discoverFiles(absolutePath);
|
|
413
|
+
|
|
414
|
+
// Filter out package.busy.md itself (handled separately)
|
|
415
|
+
const filesToCopy = discoveredFiles
|
|
416
|
+
.filter(f => path.basename(f) !== 'package.busy.md')
|
|
417
|
+
.map(f => ({
|
|
418
|
+
relativePath: path.relative(absolutePath, f),
|
|
419
|
+
absolutePath: f,
|
|
420
|
+
}));
|
|
421
|
+
|
|
422
|
+
// Initialize cache
|
|
423
|
+
const cache = new CacheManager(workspaceRoot);
|
|
424
|
+
await cache.init();
|
|
425
|
+
|
|
426
|
+
// Copy files
|
|
427
|
+
let combinedContent = '';
|
|
428
|
+
const documents: PackageDocument[] = [];
|
|
429
|
+
|
|
430
|
+
for (const file of filesToCopy) {
|
|
431
|
+
try {
|
|
432
|
+
const content = await fs.readFile(file.absolutePath, 'utf-8');
|
|
433
|
+
combinedContent += content;
|
|
434
|
+
|
|
435
|
+
const cachePath = path.join(packageName, file.relativePath);
|
|
436
|
+
await cache.save(cachePath, content);
|
|
437
|
+
|
|
438
|
+
documents.push({
|
|
439
|
+
name: path.basename(file.relativePath, path.extname(file.relativePath)),
|
|
440
|
+
relativePath: './' + file.relativePath,
|
|
441
|
+
});
|
|
442
|
+
} catch (error) {
|
|
443
|
+
console.warn(`Warning: Failed to read ${file.absolutePath}: ${error}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Save manifest if it exists
|
|
448
|
+
if (manifestContent) {
|
|
449
|
+
await cache.save(path.join(packageName, 'package.busy.md'), manifestContent);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Calculate integrity
|
|
453
|
+
const integrity = calculateIntegrity(combinedContent);
|
|
454
|
+
|
|
455
|
+
// Add to registry
|
|
456
|
+
const registry = new PackageRegistry(workspaceRoot);
|
|
457
|
+
try {
|
|
458
|
+
await registry.load();
|
|
459
|
+
} catch {
|
|
460
|
+
await registry.init();
|
|
461
|
+
await registry.load();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const entry: PackageEntry = {
|
|
465
|
+
id: packageName,
|
|
466
|
+
description: manifest?.description || '',
|
|
467
|
+
source: absolutePath,
|
|
468
|
+
provider: 'local',
|
|
469
|
+
cached: `.libraries/${packageName}`,
|
|
470
|
+
version: manifest?.version || 'latest',
|
|
471
|
+
fetched: new Date().toISOString(),
|
|
472
|
+
integrity,
|
|
473
|
+
category: 'Packages',
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
registry.addPackage(entry);
|
|
477
|
+
await registry.save();
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
name: packageName,
|
|
481
|
+
version: manifest?.version || 'latest',
|
|
482
|
+
description: manifest?.description || '',
|
|
483
|
+
documents,
|
|
484
|
+
cached: `.libraries/${packageName}`,
|
|
485
|
+
integrity,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
@@ -303,7 +303,7 @@ function createOperation(
|
|
|
303
303
|
// Parse steps and checklist from content
|
|
304
304
|
const { steps, checklist } = parseOperationContent(section);
|
|
305
305
|
|
|
306
|
-
// Get extends from section heading (e.g., ## [ValidateInput][
|
|
306
|
+
// Get extends from section heading (e.g., ## [ValidateInput][SomeType])
|
|
307
307
|
const extends_ = getSectionExtends(section.id);
|
|
308
308
|
|
|
309
309
|
return {
|