@shrkcrft/workspace 0.1.0-alpha.2
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/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/folder-scanner.d.ts +13 -0
- package/dist/folder-scanner.d.ts.map +1 -0
- package/dist/folder-scanner.js +73 -0
- package/dist/framework-detector.d.ts +9 -0
- package/dist/framework-detector.d.ts.map +1 -0
- package/dist/framework-detector.js +45 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/package-json-reader.d.ts +21 -0
- package/dist/package-json-reader.d.ts.map +1 -0
- package/dist/package-json-reader.js +18 -0
- package/dist/package-manager-detector.d.ts +15 -0
- package/dist/package-manager-detector.d.ts.map +1 -0
- package/dist/package-manager-detector.js +47 -0
- package/dist/profile-detector.d.ts +47 -0
- package/dist/profile-detector.d.ts.map +1 -0
- package/dist/profile-detector.js +162 -0
- package/dist/project-root-detector.d.ts +6 -0
- package/dist/project-root-detector.d.ts.map +1 -0
- package/dist/project-root-detector.js +22 -0
- package/dist/project-shape.d.ts +57 -0
- package/dist/project-shape.d.ts.map +1 -0
- package/dist/project-shape.js +212 -0
- package/dist/tsconfig-reader.d.ts +12 -0
- package/dist/tsconfig-reader.d.ts.map +1 -0
- package/dist/tsconfig-reader.js +37 -0
- package/dist/workspace-inspector.d.ts +7 -0
- package/dist/workspace-inspector.d.ts.map +1 -0
- package/dist/workspace-inspector.js +57 -0
- package/dist/workspace-summary.d.ts +29 -0
- package/dist/workspace-summary.d.ts.map +1 -0
- package/dist/workspace-summary.js +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SharkCraft contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @shrkcrft/workspace
|
|
2
|
+
|
|
3
|
+
SharkCraft workspace inspector: project root, package.json, package manager, frameworks, tsconfig.
|
|
4
|
+
|
|
5
|
+
Part of [SharkCraft](https://github.com/shrkcrft/sharkcraft) — a deterministic, local-first toolkit that gives AI coding agents durable project context. See the main repo for documentation, examples, and the `shrk` CLI.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @shrkcrft/workspace
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## License
|
|
14
|
+
|
|
15
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface IFolderInfo {
|
|
2
|
+
path: string;
|
|
3
|
+
exists: boolean;
|
|
4
|
+
files: number;
|
|
5
|
+
dirs: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function shallowScanFolder(dir: string): IFolderInfo;
|
|
8
|
+
export declare function listTopLevelDirs(projectRoot: string, limit?: number): string[];
|
|
9
|
+
export declare function findFiles(startDir: string, pattern: RegExp, options?: {
|
|
10
|
+
maxDepth?: number;
|
|
11
|
+
ignore?: Set<string>;
|
|
12
|
+
}): string[];
|
|
13
|
+
//# sourceMappingURL=folder-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folder-scanner.d.ts","sourceRoot":"","sources":["../src/folder-scanner.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAeD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAc1D;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,EAAE,CAU1E;AAED,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAO,GACxD,MAAM,EAAE,CAoBV"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
const DEFAULT_IGNORE = new Set([
|
|
4
|
+
'node_modules',
|
|
5
|
+
'.git',
|
|
6
|
+
'.idea',
|
|
7
|
+
'.vscode',
|
|
8
|
+
'dist',
|
|
9
|
+
'build',
|
|
10
|
+
'.cache',
|
|
11
|
+
'.nx',
|
|
12
|
+
'.turbo',
|
|
13
|
+
'coverage',
|
|
14
|
+
]);
|
|
15
|
+
export function shallowScanFolder(dir) {
|
|
16
|
+
if (!existsSync(dir))
|
|
17
|
+
return { path: dir, exists: false, files: 0, dirs: 0 };
|
|
18
|
+
try {
|
|
19
|
+
let files = 0;
|
|
20
|
+
let dirs = 0;
|
|
21
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
22
|
+
for (const e of entries) {
|
|
23
|
+
if (e.isDirectory())
|
|
24
|
+
dirs += 1;
|
|
25
|
+
else if (e.isFile())
|
|
26
|
+
files += 1;
|
|
27
|
+
}
|
|
28
|
+
return { path: dir, exists: true, files, dirs };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { path: dir, exists: true, files: 0, dirs: 0 };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function listTopLevelDirs(projectRoot, limit = 40) {
|
|
35
|
+
if (!existsSync(projectRoot))
|
|
36
|
+
return [];
|
|
37
|
+
try {
|
|
38
|
+
return readdirSync(projectRoot, { withFileTypes: true })
|
|
39
|
+
.filter((e) => e.isDirectory() && !DEFAULT_IGNORE.has(e.name))
|
|
40
|
+
.map((e) => e.name)
|
|
41
|
+
.slice(0, limit);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function findFiles(startDir, pattern, options = {}) {
|
|
48
|
+
const { maxDepth = 4, ignore = DEFAULT_IGNORE } = options;
|
|
49
|
+
const out = [];
|
|
50
|
+
function walk(dir, depth) {
|
|
51
|
+
if (depth > maxDepth)
|
|
52
|
+
return;
|
|
53
|
+
try {
|
|
54
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
55
|
+
for (const e of entries) {
|
|
56
|
+
const name = String(e.name);
|
|
57
|
+
if (ignore.has(name))
|
|
58
|
+
continue;
|
|
59
|
+
const full = nodePath.join(dir, name);
|
|
60
|
+
if (e.isDirectory())
|
|
61
|
+
walk(full, depth + 1);
|
|
62
|
+
else if (e.isFile() && pattern.test(name))
|
|
63
|
+
out.push(full);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (existsSync(startDir) && statSync(startDir).isDirectory())
|
|
71
|
+
walk(startDir, 0);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IPackageJson } from './package-json-reader.js';
|
|
2
|
+
export interface IFrameworkInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
version?: string;
|
|
6
|
+
evidence: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function detectFrameworks(projectRoot: string, pkg: IPackageJson | null): IFrameworkInfo[];
|
|
9
|
+
//# sourceMappingURL=framework-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework-detector.d.ts","sourceRoot":"","sources":["../src/framework-detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA0BD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI,GAAG,cAAc,EAAE,CA6BhG"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
const FRAMEWORKS = [
|
|
4
|
+
{ id: 'angular', name: 'Angular', packages: ['@angular/core', '@angular/cli'], fileMarkers: ['angular.json'] },
|
|
5
|
+
{ id: 'react', name: 'React', packages: ['react'] },
|
|
6
|
+
{ id: 'vue', name: 'Vue', packages: ['vue'] },
|
|
7
|
+
{ id: 'svelte', name: 'Svelte', packages: ['svelte'] },
|
|
8
|
+
{ id: 'nextjs', name: 'Next.js', packages: ['next'] },
|
|
9
|
+
{ id: 'nuxt', name: 'Nuxt', packages: ['nuxt'] },
|
|
10
|
+
{ id: 'nestjs', name: 'NestJS', packages: ['@nestjs/core'] },
|
|
11
|
+
{ id: 'express', name: 'Express', packages: ['express'] },
|
|
12
|
+
{ id: 'fastify', name: 'Fastify', packages: ['fastify'] },
|
|
13
|
+
{ id: 'nx', name: 'Nx', packages: ['nx', '@nx/workspace'], fileMarkers: ['nx.json'] },
|
|
14
|
+
{ id: 'aws-lambda', name: 'AWS Lambda', packages: ['aws-lambda', '@types/aws-lambda'] },
|
|
15
|
+
{ id: 'electron', name: 'Electron', packages: ['electron'] },
|
|
16
|
+
{ id: 'typescript', name: 'TypeScript', packages: ['typescript'], fileMarkers: ['tsconfig.json', 'tsconfig.base.json'] },
|
|
17
|
+
{ id: 'bun', name: 'Bun', packages: ['bun-types', '@types/bun'], fileMarkers: ['bun.lockb', 'bun.lock'] },
|
|
18
|
+
];
|
|
19
|
+
export function detectFrameworks(projectRoot, pkg) {
|
|
20
|
+
const out = [];
|
|
21
|
+
const allDeps = {
|
|
22
|
+
...(pkg?.dependencies ?? {}),
|
|
23
|
+
...(pkg?.devDependencies ?? {}),
|
|
24
|
+
...(pkg?.peerDependencies ?? {}),
|
|
25
|
+
};
|
|
26
|
+
for (const def of FRAMEWORKS) {
|
|
27
|
+
const evidence = [];
|
|
28
|
+
let version;
|
|
29
|
+
for (const pkgName of def.packages) {
|
|
30
|
+
if (pkgName in allDeps) {
|
|
31
|
+
evidence.push(`depends on ${pkgName}`);
|
|
32
|
+
version = version ?? allDeps[pkgName];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const marker of def.fileMarkers ?? []) {
|
|
36
|
+
if (existsSync(nodePath.join(projectRoot, marker))) {
|
|
37
|
+
evidence.push(`${marker} exists`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (evidence.length > 0) {
|
|
41
|
+
out.push({ id: def.id, name: def.name, version, evidence });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './project-root-detector.js';
|
|
2
|
+
export * from './package-json-reader.js';
|
|
3
|
+
export * from './package-manager-detector.js';
|
|
4
|
+
export * from './framework-detector.js';
|
|
5
|
+
export * from './tsconfig-reader.js';
|
|
6
|
+
export * from './folder-scanner.js';
|
|
7
|
+
export * from './profile-detector.js';
|
|
8
|
+
export * from './workspace-summary.js';
|
|
9
|
+
export * from './workspace-inspector.js';
|
|
10
|
+
export * from './project-shape.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./project-root-detector.js";
|
|
2
|
+
export * from "./package-json-reader.js";
|
|
3
|
+
export * from "./package-manager-detector.js";
|
|
4
|
+
export * from "./framework-detector.js";
|
|
5
|
+
export * from "./tsconfig-reader.js";
|
|
6
|
+
export * from "./folder-scanner.js";
|
|
7
|
+
export * from "./profile-detector.js";
|
|
8
|
+
export * from "./workspace-summary.js";
|
|
9
|
+
export * from "./workspace-inspector.js";
|
|
10
|
+
export * from "./project-shape.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type AppError, type Result } from '@shrkcrft/core';
|
|
2
|
+
export interface IPackageJson {
|
|
3
|
+
name?: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
private?: boolean;
|
|
7
|
+
type?: 'module' | 'commonjs';
|
|
8
|
+
scripts?: Record<string, string>;
|
|
9
|
+
dependencies?: Record<string, string>;
|
|
10
|
+
devDependencies?: Record<string, string>;
|
|
11
|
+
peerDependencies?: Record<string, string>;
|
|
12
|
+
workspaces?: string[] | {
|
|
13
|
+
packages?: string[];
|
|
14
|
+
};
|
|
15
|
+
packageManager?: string;
|
|
16
|
+
engines?: Record<string, string>;
|
|
17
|
+
bin?: string | Record<string, string>;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
export declare function readPackageJson(projectRoot: string): Result<IPackageJson | null, AppError>;
|
|
21
|
+
//# sourceMappingURL=package-json-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-json-reader.d.ts","sourceRoot":"","sources":["../src/package-json-reader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAEhG,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,YAAY,GAAG,IAAI,EAAE,QAAQ,CAAC,CAc1F"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
import { AppErrorImpl, ERROR_CODES, err, ok } from '@shrkcrft/core';
|
|
4
|
+
export function readPackageJson(projectRoot) {
|
|
5
|
+
const pkgPath = nodePath.join(projectRoot, 'package.json');
|
|
6
|
+
if (!existsSync(pkgPath))
|
|
7
|
+
return ok(null);
|
|
8
|
+
try {
|
|
9
|
+
const raw = readFileSync(pkgPath, 'utf8');
|
|
10
|
+
return ok(JSON.parse(raw));
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
return err(new AppErrorImpl(ERROR_CODES.FILE_READ_ERROR, `Failed to parse package.json: ${pkgPath}`, {
|
|
14
|
+
details: { pkgPath },
|
|
15
|
+
cause: e,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IPackageJson } from './package-json-reader.js';
|
|
2
|
+
export declare enum PackageManager {
|
|
3
|
+
Bun = "bun",
|
|
4
|
+
Pnpm = "pnpm",
|
|
5
|
+
Yarn = "yarn",
|
|
6
|
+
Npm = "npm",
|
|
7
|
+
Unknown = "unknown"
|
|
8
|
+
}
|
|
9
|
+
export interface IPackageManagerInfo {
|
|
10
|
+
manager: PackageManager;
|
|
11
|
+
version?: string;
|
|
12
|
+
evidence: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function detectPackageManager(projectRoot: string, pkg: IPackageJson | null): IPackageManagerInfo;
|
|
15
|
+
//# sourceMappingURL=package-manager-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-manager-detector.d.ts","sourceRoot":"","sources":["../src/package-manager-detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,oBAAY,cAAc;IACxB,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI,GAAG,mBAAmB,CAmCvG"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
export var PackageManager;
|
|
4
|
+
(function (PackageManager) {
|
|
5
|
+
PackageManager["Bun"] = "bun";
|
|
6
|
+
PackageManager["Pnpm"] = "pnpm";
|
|
7
|
+
PackageManager["Yarn"] = "yarn";
|
|
8
|
+
PackageManager["Npm"] = "npm";
|
|
9
|
+
PackageManager["Unknown"] = "unknown";
|
|
10
|
+
})(PackageManager || (PackageManager = {}));
|
|
11
|
+
export function detectPackageManager(projectRoot, pkg) {
|
|
12
|
+
const evidence = [];
|
|
13
|
+
if (pkg?.packageManager) {
|
|
14
|
+
const [name, version] = pkg.packageManager.split('@');
|
|
15
|
+
evidence.push(`packageManager field: ${pkg.packageManager}`);
|
|
16
|
+
const manager = (name ?? '').toLowerCase();
|
|
17
|
+
if (manager === 'bun')
|
|
18
|
+
return { manager: PackageManager.Bun, version, evidence };
|
|
19
|
+
if (manager === 'pnpm')
|
|
20
|
+
return { manager: PackageManager.Pnpm, version, evidence };
|
|
21
|
+
if (manager === 'yarn')
|
|
22
|
+
return { manager: PackageManager.Yarn, version, evidence };
|
|
23
|
+
if (manager === 'npm')
|
|
24
|
+
return { manager: PackageManager.Npm, version, evidence };
|
|
25
|
+
}
|
|
26
|
+
if (existsSync(nodePath.join(projectRoot, 'bun.lockb'))) {
|
|
27
|
+
evidence.push('bun.lockb present');
|
|
28
|
+
return { manager: PackageManager.Bun, evidence };
|
|
29
|
+
}
|
|
30
|
+
if (existsSync(nodePath.join(projectRoot, 'bun.lock'))) {
|
|
31
|
+
evidence.push('bun.lock present');
|
|
32
|
+
return { manager: PackageManager.Bun, evidence };
|
|
33
|
+
}
|
|
34
|
+
if (existsSync(nodePath.join(projectRoot, 'pnpm-lock.yaml'))) {
|
|
35
|
+
evidence.push('pnpm-lock.yaml present');
|
|
36
|
+
return { manager: PackageManager.Pnpm, evidence };
|
|
37
|
+
}
|
|
38
|
+
if (existsSync(nodePath.join(projectRoot, 'yarn.lock'))) {
|
|
39
|
+
evidence.push('yarn.lock present');
|
|
40
|
+
return { manager: PackageManager.Yarn, evidence };
|
|
41
|
+
}
|
|
42
|
+
if (existsSync(nodePath.join(projectRoot, 'package-lock.json'))) {
|
|
43
|
+
evidence.push('package-lock.json present');
|
|
44
|
+
return { manager: PackageManager.Npm, evidence };
|
|
45
|
+
}
|
|
46
|
+
return { manager: PackageManager.Unknown, evidence };
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { IPackageJson } from './package-json-reader.js';
|
|
2
|
+
import type { IFrameworkInfo } from './framework-detector.js';
|
|
3
|
+
export declare enum WorkspaceProfile {
|
|
4
|
+
HasBun = "has-bun",
|
|
5
|
+
HasTypeScript = "has-typescript",
|
|
6
|
+
HasNx = "has-nx",
|
|
7
|
+
HasTurborepo = "has-turborepo",
|
|
8
|
+
HasReact = "has-react",
|
|
9
|
+
HasNext = "has-next",
|
|
10
|
+
HasAngular = "has-angular",
|
|
11
|
+
HasVue = "has-vue",
|
|
12
|
+
HasNestJS = "has-nestjs",
|
|
13
|
+
HasMcpSdk = "has-mcp-sdk",
|
|
14
|
+
HasTests = "has-tests",
|
|
15
|
+
HasEslint = "has-eslint",
|
|
16
|
+
HasBiome = "has-biome",
|
|
17
|
+
HasVitest = "has-vitest",
|
|
18
|
+
HasJest = "has-jest",
|
|
19
|
+
HasBunTest = "has-bun-test",
|
|
20
|
+
HasGithubActions = "has-github-actions",
|
|
21
|
+
HasPackageWorkspaces = "has-package-workspaces",
|
|
22
|
+
IsLibrary = "is-library",
|
|
23
|
+
IsService = "is-service",
|
|
24
|
+
IsMonorepo = "is-monorepo",
|
|
25
|
+
IsFrontend = "is-frontend",
|
|
26
|
+
IsBackend = "is-backend"
|
|
27
|
+
}
|
|
28
|
+
export interface IProfileEvidence {
|
|
29
|
+
profile: WorkspaceProfile;
|
|
30
|
+
reason: string;
|
|
31
|
+
}
|
|
32
|
+
export interface IProfileDetectionResult {
|
|
33
|
+
profiles: WorkspaceProfile[];
|
|
34
|
+
evidence: IProfileEvidence[];
|
|
35
|
+
}
|
|
36
|
+
export interface IDetectProfilesInput {
|
|
37
|
+
packageJson: IPackageJson | null;
|
|
38
|
+
frameworks: readonly IFrameworkInfo[];
|
|
39
|
+
topLevelDirs: readonly string[];
|
|
40
|
+
hasTsConfig: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Compute structured profile tags from a workspace inspection. Pure function:
|
|
44
|
+
* no I/O. Each detected profile has an `evidence` entry explaining why.
|
|
45
|
+
*/
|
|
46
|
+
export declare function detectProfiles(input: IDetectProfilesInput): IProfileDetectionResult;
|
|
47
|
+
//# sourceMappingURL=profile-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-detector.d.ts","sourceRoot":"","sources":["../src/profile-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,oBAAY,gBAAgB;IAC1B,MAAM,YAAY;IAClB,aAAa,mBAAmB;IAChC,KAAK,WAAW;IAChB,YAAY,kBAAkB;IAC9B,QAAQ,cAAc;IACtB,OAAO,aAAa;IACpB,UAAU,gBAAgB;IAC1B,MAAM,YAAY;IAClB,SAAS,eAAe;IACxB,SAAS,gBAAgB;IACzB,QAAQ,cAAc;IACtB,SAAS,eAAe;IACxB,QAAQ,cAAc;IACtB,SAAS,eAAe;IACxB,OAAO,aAAa;IACpB,UAAU,iBAAiB;IAC3B,gBAAgB,uBAAuB;IACvC,oBAAoB,2BAA2B;IAC/C,SAAS,eAAe;IACxB,SAAS,eAAe;IACxB,UAAU,gBAAgB;IAC1B,UAAU,gBAAgB;IAC1B,SAAS,eAAe;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,SAAS,cAAc,EAAE,CAAC;IACtC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,WAAW,EAAE,OAAO,CAAC;CACtB;AA6BD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,GAAG,uBAAuB,CAuInF"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
export var WorkspaceProfile;
|
|
2
|
+
(function (WorkspaceProfile) {
|
|
3
|
+
WorkspaceProfile["HasBun"] = "has-bun";
|
|
4
|
+
WorkspaceProfile["HasTypeScript"] = "has-typescript";
|
|
5
|
+
WorkspaceProfile["HasNx"] = "has-nx";
|
|
6
|
+
WorkspaceProfile["HasTurborepo"] = "has-turborepo";
|
|
7
|
+
WorkspaceProfile["HasReact"] = "has-react";
|
|
8
|
+
WorkspaceProfile["HasNext"] = "has-next";
|
|
9
|
+
WorkspaceProfile["HasAngular"] = "has-angular";
|
|
10
|
+
WorkspaceProfile["HasVue"] = "has-vue";
|
|
11
|
+
WorkspaceProfile["HasNestJS"] = "has-nestjs";
|
|
12
|
+
WorkspaceProfile["HasMcpSdk"] = "has-mcp-sdk";
|
|
13
|
+
WorkspaceProfile["HasTests"] = "has-tests";
|
|
14
|
+
WorkspaceProfile["HasEslint"] = "has-eslint";
|
|
15
|
+
WorkspaceProfile["HasBiome"] = "has-biome";
|
|
16
|
+
WorkspaceProfile["HasVitest"] = "has-vitest";
|
|
17
|
+
WorkspaceProfile["HasJest"] = "has-jest";
|
|
18
|
+
WorkspaceProfile["HasBunTest"] = "has-bun-test";
|
|
19
|
+
WorkspaceProfile["HasGithubActions"] = "has-github-actions";
|
|
20
|
+
WorkspaceProfile["HasPackageWorkspaces"] = "has-package-workspaces";
|
|
21
|
+
WorkspaceProfile["IsLibrary"] = "is-library";
|
|
22
|
+
WorkspaceProfile["IsService"] = "is-service";
|
|
23
|
+
WorkspaceProfile["IsMonorepo"] = "is-monorepo";
|
|
24
|
+
WorkspaceProfile["IsFrontend"] = "is-frontend";
|
|
25
|
+
WorkspaceProfile["IsBackend"] = "is-backend";
|
|
26
|
+
})(WorkspaceProfile || (WorkspaceProfile = {}));
|
|
27
|
+
function hasDep(pkg, name) {
|
|
28
|
+
if (!pkg)
|
|
29
|
+
return false;
|
|
30
|
+
return (Boolean(pkg.dependencies?.[name]) ||
|
|
31
|
+
Boolean(pkg.devDependencies?.[name]) ||
|
|
32
|
+
Boolean(pkg.peerDependencies?.[name]));
|
|
33
|
+
}
|
|
34
|
+
function hasAnyDep(pkg, names) {
|
|
35
|
+
return names.some((n) => hasDep(pkg, n));
|
|
36
|
+
}
|
|
37
|
+
function hasFramework(frameworks, id) {
|
|
38
|
+
return frameworks.some((f) => f.id === id);
|
|
39
|
+
}
|
|
40
|
+
function hasScriptIncluding(pkg, ...needles) {
|
|
41
|
+
if (!pkg)
|
|
42
|
+
return false;
|
|
43
|
+
for (const v of Object.values(pkg.scripts ?? {})) {
|
|
44
|
+
for (const n of needles) {
|
|
45
|
+
if (typeof v === 'string' && v.includes(n))
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Compute structured profile tags from a workspace inspection. Pure function:
|
|
53
|
+
* no I/O. Each detected profile has an `evidence` entry explaining why.
|
|
54
|
+
*/
|
|
55
|
+
export function detectProfiles(input) {
|
|
56
|
+
const { packageJson: pkg, frameworks, topLevelDirs, hasTsConfig } = input;
|
|
57
|
+
const evidence = [];
|
|
58
|
+
const add = (profile, reason) => {
|
|
59
|
+
if (!evidence.some((e) => e.profile === profile)) {
|
|
60
|
+
evidence.push({ profile, reason });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
// ── Language / runtime ────────────────────────────────────────────────
|
|
64
|
+
if (hasFramework(frameworks, 'bun') || hasDep(pkg, 'bun') || hasDep(pkg, '@types/bun')) {
|
|
65
|
+
add(WorkspaceProfile.HasBun, 'bun runtime detected via deps or framework signal');
|
|
66
|
+
}
|
|
67
|
+
if (hasTsConfig || hasDep(pkg, 'typescript') || hasFramework(frameworks, 'typescript')) {
|
|
68
|
+
add(WorkspaceProfile.HasTypeScript, 'tsconfig.json or typescript dependency present');
|
|
69
|
+
}
|
|
70
|
+
// ── Build / workspace tooling ─────────────────────────────────────────
|
|
71
|
+
if (hasFramework(frameworks, 'nx') || hasDep(pkg, 'nx') || hasDep(pkg, '@nx/workspace')) {
|
|
72
|
+
add(WorkspaceProfile.HasNx, 'nx workspace detected');
|
|
73
|
+
}
|
|
74
|
+
if (hasDep(pkg, 'turbo') ||
|
|
75
|
+
topLevelDirs.includes('turbo.json') ||
|
|
76
|
+
topLevelDirs.includes('.turbo')) {
|
|
77
|
+
add(WorkspaceProfile.HasTurborepo, 'turbo dependency or turbo.json present');
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(pkg?.workspaces)) {
|
|
80
|
+
add(WorkspaceProfile.HasPackageWorkspaces, 'package.json workspaces array');
|
|
81
|
+
}
|
|
82
|
+
if (hasFramework(frameworks, 'nx') ||
|
|
83
|
+
Array.isArray(pkg?.workspaces) ||
|
|
84
|
+
topLevelDirs.includes('packages') ||
|
|
85
|
+
topLevelDirs.includes('libs') ||
|
|
86
|
+
topLevelDirs.includes('apps')) {
|
|
87
|
+
add(WorkspaceProfile.IsMonorepo, 'workspaces / Nx / packages/libs dirs present');
|
|
88
|
+
}
|
|
89
|
+
// ── UI frameworks ─────────────────────────────────────────────────────
|
|
90
|
+
if (hasFramework(frameworks, 'react') ||
|
|
91
|
+
hasAnyDep(pkg, ['react', 'react-dom', 'next', '@remix-run/react'])) {
|
|
92
|
+
add(WorkspaceProfile.HasReact, 'react family dependency or framework signal');
|
|
93
|
+
}
|
|
94
|
+
if (hasFramework(frameworks, 'next') || hasDep(pkg, 'next')) {
|
|
95
|
+
add(WorkspaceProfile.HasNext, 'next dependency or framework signal');
|
|
96
|
+
}
|
|
97
|
+
if (hasFramework(frameworks, 'angular') ||
|
|
98
|
+
hasAnyDep(pkg, ['@angular/core', '@angular/cli'])) {
|
|
99
|
+
add(WorkspaceProfile.HasAngular, '@angular/* dependency detected');
|
|
100
|
+
}
|
|
101
|
+
if (hasFramework(frameworks, 'vue') || hasAnyDep(pkg, ['vue', 'nuxt'])) {
|
|
102
|
+
add(WorkspaceProfile.HasVue, 'vue / nuxt dependency');
|
|
103
|
+
}
|
|
104
|
+
// ── Backend ───────────────────────────────────────────────────────────
|
|
105
|
+
if (hasFramework(frameworks, 'nestjs') ||
|
|
106
|
+
hasAnyDep(pkg, ['@nestjs/core', '@nestjs/common'])) {
|
|
107
|
+
add(WorkspaceProfile.HasNestJS, '@nestjs/* dependency');
|
|
108
|
+
}
|
|
109
|
+
if (hasDep(pkg, '@modelcontextprotocol/sdk')) {
|
|
110
|
+
add(WorkspaceProfile.HasMcpSdk, '@modelcontextprotocol/sdk dependency');
|
|
111
|
+
}
|
|
112
|
+
// ── Testing ───────────────────────────────────────────────────────────
|
|
113
|
+
if (hasDep(pkg, 'vitest'))
|
|
114
|
+
add(WorkspaceProfile.HasVitest, 'vitest dependency');
|
|
115
|
+
if (hasAnyDep(pkg, ['jest', '@jest/globals']))
|
|
116
|
+
add(WorkspaceProfile.HasJest, 'jest dependency');
|
|
117
|
+
if (hasScriptIncluding(pkg, 'bun test', 'bun:test') ||
|
|
118
|
+
(hasDep(pkg, '@types/bun') && hasScriptIncluding(pkg, 'test'))) {
|
|
119
|
+
add(WorkspaceProfile.HasBunTest, 'bun test script detected');
|
|
120
|
+
}
|
|
121
|
+
if (evidence.some((e) => e.profile === WorkspaceProfile.HasVitest) ||
|
|
122
|
+
evidence.some((e) => e.profile === WorkspaceProfile.HasJest) ||
|
|
123
|
+
evidence.some((e) => e.profile === WorkspaceProfile.HasBunTest) ||
|
|
124
|
+
hasScriptIncluding(pkg, 'test') ||
|
|
125
|
+
topLevelDirs.some((d) => d === 'tests' || d === '__tests__')) {
|
|
126
|
+
add(WorkspaceProfile.HasTests, 'test runner or test directory present');
|
|
127
|
+
}
|
|
128
|
+
// ── Lint ──────────────────────────────────────────────────────────────
|
|
129
|
+
if (hasAnyDep(pkg, ['eslint', '@eslint/js'])) {
|
|
130
|
+
add(WorkspaceProfile.HasEslint, 'eslint dependency');
|
|
131
|
+
}
|
|
132
|
+
if (hasAnyDep(pkg, ['@biomejs/biome'])) {
|
|
133
|
+
add(WorkspaceProfile.HasBiome, '@biomejs/biome dependency');
|
|
134
|
+
}
|
|
135
|
+
// ── CI ────────────────────────────────────────────────────────────────
|
|
136
|
+
if (topLevelDirs.includes('.github')) {
|
|
137
|
+
add(WorkspaceProfile.HasGithubActions, '.github directory present (likely workflows)');
|
|
138
|
+
}
|
|
139
|
+
// ── Library vs service vs frontend/backend ────────────────────────────
|
|
140
|
+
const looksLibrary = !!pkg?.main || !!pkg?.exports || !!pkg?.types;
|
|
141
|
+
if (looksLibrary && !topLevelDirs.includes('apps')) {
|
|
142
|
+
add(WorkspaceProfile.IsLibrary, 'package.json declares main/exports/types');
|
|
143
|
+
}
|
|
144
|
+
if (hasScriptIncluding(pkg, 'start', 'serve', 'dev:server') &&
|
|
145
|
+
!evidence.some((e) => e.profile === WorkspaceProfile.IsLibrary)) {
|
|
146
|
+
add(WorkspaceProfile.IsService, 'start/serve script detected');
|
|
147
|
+
}
|
|
148
|
+
const frontendSignal = evidence.some((e) => [WorkspaceProfile.HasReact, WorkspaceProfile.HasAngular, WorkspaceProfile.HasVue].includes(e.profile)) ||
|
|
149
|
+
hasAnyDep(pkg, ['vite', '@angular/build', 'next', 'remix']);
|
|
150
|
+
if (frontendSignal) {
|
|
151
|
+
add(WorkspaceProfile.IsFrontend, 'UI framework or frontend bundler dependency');
|
|
152
|
+
}
|
|
153
|
+
const backendSignal = evidence.some((e) => e.profile === WorkspaceProfile.HasNestJS) ||
|
|
154
|
+
hasAnyDep(pkg, ['express', 'fastify', 'koa', 'hono', '@nestjs/core']);
|
|
155
|
+
if (backendSignal) {
|
|
156
|
+
add(WorkspaceProfile.IsBackend, 'HTTP server framework dependency');
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
profiles: evidence.map((e) => e.profile),
|
|
160
|
+
evidence,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-root-detector.d.ts","sourceRoot":"","sources":["../src/project-root-detector.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAWD,wBAAgB,iBAAiB,CAAC,QAAQ,GAAE,MAAsB,GAAG,YAAY,CAShF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
const ROOT_MARKERS = [
|
|
4
|
+
'package.json',
|
|
5
|
+
'bun.lockb',
|
|
6
|
+
'pnpm-workspace.yaml',
|
|
7
|
+
'nx.json',
|
|
8
|
+
'tsconfig.base.json',
|
|
9
|
+
'.git',
|
|
10
|
+
];
|
|
11
|
+
export function detectProjectRoot(startDir = process.cwd()) {
|
|
12
|
+
let current = nodePath.resolve(startDir);
|
|
13
|
+
while (true) {
|
|
14
|
+
const found = ROOT_MARKERS.filter((m) => existsSync(nodePath.join(current, m)));
|
|
15
|
+
if (found.length > 0)
|
|
16
|
+
return { root: current, markers: found };
|
|
17
|
+
const parent = nodePath.dirname(current);
|
|
18
|
+
if (parent === current)
|
|
19
|
+
return { root: nodePath.resolve(startDir), markers: [] };
|
|
20
|
+
current = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { IPackageJson } from './package-json-reader.js';
|
|
2
|
+
/**
|
|
3
|
+
* Coarse project-shape classification. Drives the default
|
|
4
|
+
* surface composition: a single-app repo hides monorepo-only
|
|
5
|
+
* commands by default; a library repo hides app-only commands.
|
|
6
|
+
*/
|
|
7
|
+
export declare enum ProjectShape {
|
|
8
|
+
SingleApp = "single-app",
|
|
9
|
+
AppWithLibs = "app-with-libs",
|
|
10
|
+
Monorepo = "monorepo",
|
|
11
|
+
Library = "library",
|
|
12
|
+
Unknown = "unknown"
|
|
13
|
+
}
|
|
14
|
+
export interface IProjectShapeDetection {
|
|
15
|
+
shape: ProjectShape;
|
|
16
|
+
evidence: readonly string[];
|
|
17
|
+
/** Hint signals the resolver collected during detection. */
|
|
18
|
+
signals: {
|
|
19
|
+
hasAngularJson: boolean;
|
|
20
|
+
hasNxJson: boolean;
|
|
21
|
+
nxProjectCount: number | null;
|
|
22
|
+
workspaceCount: number | null;
|
|
23
|
+
hasAppsDir: boolean;
|
|
24
|
+
hasLibsDir: boolean;
|
|
25
|
+
hasDevServeScript: boolean;
|
|
26
|
+
hasOnlyBuildTestScripts: boolean;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface DetectProjectShapeOptions {
|
|
30
|
+
projectRoot: string;
|
|
31
|
+
/** Pre-loaded package.json, if available. */
|
|
32
|
+
packageJson?: IPackageJson | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Deterministic project-shape detector. No AI, no heuristics
|
|
36
|
+
* beyond file/dependency presence. Result is cacheable.
|
|
37
|
+
*
|
|
38
|
+
* Rules (first match wins; subsequent signals contribute evidence
|
|
39
|
+
* but do not change the verdict):
|
|
40
|
+
*
|
|
41
|
+
* 1. `nx.json` present AND ≥6 Nx projects discovered → Monorepo.
|
|
42
|
+
* 2. `package.json workspaces` field with ≥3 entries → Monorepo.
|
|
43
|
+
* 3. `angular.json` workspace with exactly one project → SingleApp.
|
|
44
|
+
* 4. `apps/` AND `libs/` dirs present (Nx-style) → AppWithLibs.
|
|
45
|
+
* 5. `package.json` with build/test scripts but NO dev/serve/start
|
|
46
|
+
* script AND no app dir → Library.
|
|
47
|
+
* 6. Otherwise → Unknown.
|
|
48
|
+
*/
|
|
49
|
+
export declare function detectProjectShape(options: DetectProjectShapeOptions): IProjectShapeDetection;
|
|
50
|
+
export interface IProjectShapeCacheEntry {
|
|
51
|
+
schema: 'sharkcraft.shape.v1';
|
|
52
|
+
detection: IProjectShapeDetection;
|
|
53
|
+
cachedAt: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function cacheProjectShape(projectRoot: string, detection: IProjectShapeDetection): string;
|
|
56
|
+
export declare function readCachedProjectShape(projectRoot: string): IProjectShapeCacheEntry | null;
|
|
57
|
+
//# sourceMappingURL=project-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-shape.d.ts","sourceRoot":"","sources":["../src/project-shape.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D;;;;GAIG;AACH,oBAAY,YAAY;IACtB,SAAS,eAAe;IACxB,WAAW,kBAAkB;IAC7B,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,4DAA4D;IAC5D,OAAO,EAAE;QACP,cAAc,EAAE,OAAO,CAAC;QACxB,SAAS,EAAE,OAAO,CAAC;QACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;QACpB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,uBAAuB,EAAE,OAAO,CAAC;KAClC,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CACnC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GACjC,sBAAsB,CAuGxB;AAkED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,SAAS,EAAE,sBAAsB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,sBAAsB,GAChC,MAAM,CAWR;AAED,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,GAClB,uBAAuB,GAAG,IAAI,CAQhC"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Coarse project-shape classification. Drives the default
|
|
5
|
+
* surface composition: a single-app repo hides monorepo-only
|
|
6
|
+
* commands by default; a library repo hides app-only commands.
|
|
7
|
+
*/
|
|
8
|
+
export var ProjectShape;
|
|
9
|
+
(function (ProjectShape) {
|
|
10
|
+
ProjectShape["SingleApp"] = "single-app";
|
|
11
|
+
ProjectShape["AppWithLibs"] = "app-with-libs";
|
|
12
|
+
ProjectShape["Monorepo"] = "monorepo";
|
|
13
|
+
ProjectShape["Library"] = "library";
|
|
14
|
+
ProjectShape["Unknown"] = "unknown";
|
|
15
|
+
})(ProjectShape || (ProjectShape = {}));
|
|
16
|
+
/**
|
|
17
|
+
* Deterministic project-shape detector. No AI, no heuristics
|
|
18
|
+
* beyond file/dependency presence. Result is cacheable.
|
|
19
|
+
*
|
|
20
|
+
* Rules (first match wins; subsequent signals contribute evidence
|
|
21
|
+
* but do not change the verdict):
|
|
22
|
+
*
|
|
23
|
+
* 1. `nx.json` present AND ≥6 Nx projects discovered → Monorepo.
|
|
24
|
+
* 2. `package.json workspaces` field with ≥3 entries → Monorepo.
|
|
25
|
+
* 3. `angular.json` workspace with exactly one project → SingleApp.
|
|
26
|
+
* 4. `apps/` AND `libs/` dirs present (Nx-style) → AppWithLibs.
|
|
27
|
+
* 5. `package.json` with build/test scripts but NO dev/serve/start
|
|
28
|
+
* script AND no app dir → Library.
|
|
29
|
+
* 6. Otherwise → Unknown.
|
|
30
|
+
*/
|
|
31
|
+
export function detectProjectShape(options) {
|
|
32
|
+
const { projectRoot, packageJson } = options;
|
|
33
|
+
const evidence = [];
|
|
34
|
+
const signals = {
|
|
35
|
+
hasAngularJson: existsSync(nodePath.join(projectRoot, 'angular.json')),
|
|
36
|
+
hasNxJson: existsSync(nodePath.join(projectRoot, 'nx.json')),
|
|
37
|
+
nxProjectCount: null,
|
|
38
|
+
workspaceCount: null,
|
|
39
|
+
hasAppsDir: existsSync(nodePath.join(projectRoot, 'apps')),
|
|
40
|
+
hasLibsDir: existsSync(nodePath.join(projectRoot, 'libs')),
|
|
41
|
+
hasDevServeScript: false,
|
|
42
|
+
hasOnlyBuildTestScripts: false,
|
|
43
|
+
};
|
|
44
|
+
// Nx project count (best-effort: read nx.json + count projects/).
|
|
45
|
+
if (signals.hasNxJson) {
|
|
46
|
+
signals.nxProjectCount = countNxProjects(projectRoot);
|
|
47
|
+
if (signals.nxProjectCount !== null) {
|
|
48
|
+
evidence.push(`nx.json present (${signals.nxProjectCount} projects)`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
evidence.push('nx.json present');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Workspaces count.
|
|
55
|
+
if (packageJson?.workspaces !== undefined) {
|
|
56
|
+
const ws = packageJson.workspaces;
|
|
57
|
+
const list = Array.isArray(ws) ? ws : (ws.packages ?? []);
|
|
58
|
+
signals.workspaceCount = list.length;
|
|
59
|
+
evidence.push(`package.json workspaces (${list.length} entries)`);
|
|
60
|
+
}
|
|
61
|
+
// Angular detection.
|
|
62
|
+
if (signals.hasAngularJson) {
|
|
63
|
+
evidence.push('angular.json present');
|
|
64
|
+
}
|
|
65
|
+
// Script-based signals.
|
|
66
|
+
const scripts = packageJson?.scripts ?? {};
|
|
67
|
+
const scriptNames = Object.keys(scripts);
|
|
68
|
+
signals.hasDevServeScript = scriptNames.some((s) => ['dev', 'serve', 'start'].includes(s));
|
|
69
|
+
signals.hasOnlyBuildTestScripts =
|
|
70
|
+
scriptNames.length > 0 &&
|
|
71
|
+
scriptNames.every((s) => /^(build|test|lint|format|prepublish|prepare)/.test(s));
|
|
72
|
+
if (signals.hasAppsDir)
|
|
73
|
+
evidence.push('apps/ directory');
|
|
74
|
+
if (signals.hasLibsDir)
|
|
75
|
+
evidence.push('libs/ directory');
|
|
76
|
+
if (signals.hasDevServeScript)
|
|
77
|
+
evidence.push('dev/serve/start script');
|
|
78
|
+
if (signals.hasOnlyBuildTestScripts)
|
|
79
|
+
evidence.push('only build/test scripts');
|
|
80
|
+
// Count workspaces-glob packages on disk (cheap dir count for
|
|
81
|
+
// `packages/`, `libs/`, `apps/` — a glob like `packages/*` matches
|
|
82
|
+
// any direct subdir).
|
|
83
|
+
const packagesDirCount = countDirectChildren(nodePath.join(projectRoot, 'packages'));
|
|
84
|
+
if (packagesDirCount > 0) {
|
|
85
|
+
evidence.push(`packages/ (${packagesDirCount} entries)`);
|
|
86
|
+
}
|
|
87
|
+
// Apply rules — strongest signals first. Conservative on SingleApp:
|
|
88
|
+
// require an unambiguous app signal (angular.json single project, or
|
|
89
|
+
// a dev script in a project with NO sibling packages and NO nx.json
|
|
90
|
+
// and NO workspaces field).
|
|
91
|
+
if (signals.hasNxJson && (signals.nxProjectCount ?? 0) >= 6) {
|
|
92
|
+
return { shape: ProjectShape.Monorepo, evidence, signals };
|
|
93
|
+
}
|
|
94
|
+
if ((signals.workspaceCount ?? 0) >= 3) {
|
|
95
|
+
return { shape: ProjectShape.Monorepo, evidence, signals };
|
|
96
|
+
}
|
|
97
|
+
if (signals.hasNxJson && packagesDirCount >= 6) {
|
|
98
|
+
return { shape: ProjectShape.Monorepo, evidence, signals };
|
|
99
|
+
}
|
|
100
|
+
if ((signals.workspaceCount ?? 0) >= 1 &&
|
|
101
|
+
packagesDirCount >= 3) {
|
|
102
|
+
return { shape: ProjectShape.Monorepo, evidence, signals };
|
|
103
|
+
}
|
|
104
|
+
if (signals.hasAngularJson) {
|
|
105
|
+
const projectCount = countAngularProjects(projectRoot);
|
|
106
|
+
if (projectCount !== null && projectCount > 1) {
|
|
107
|
+
evidence.push(`angular.json (${projectCount} projects)`);
|
|
108
|
+
return { shape: ProjectShape.AppWithLibs, evidence, signals };
|
|
109
|
+
}
|
|
110
|
+
return { shape: ProjectShape.SingleApp, evidence, signals };
|
|
111
|
+
}
|
|
112
|
+
if (signals.hasAppsDir && signals.hasLibsDir) {
|
|
113
|
+
return { shape: ProjectShape.AppWithLibs, evidence, signals };
|
|
114
|
+
}
|
|
115
|
+
if (signals.hasOnlyBuildTestScripts && !signals.hasAppsDir && packagesDirCount === 0) {
|
|
116
|
+
return { shape: ProjectShape.Library, evidence, signals };
|
|
117
|
+
}
|
|
118
|
+
if (signals.hasDevServeScript &&
|
|
119
|
+
!signals.hasLibsDir &&
|
|
120
|
+
!signals.hasNxJson &&
|
|
121
|
+
signals.workspaceCount === null &&
|
|
122
|
+
packagesDirCount === 0) {
|
|
123
|
+
return { shape: ProjectShape.SingleApp, evidence, signals };
|
|
124
|
+
}
|
|
125
|
+
return { shape: ProjectShape.Unknown, evidence, signals };
|
|
126
|
+
}
|
|
127
|
+
function countDirectChildren(dir) {
|
|
128
|
+
try {
|
|
129
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function countNxProjects(projectRoot) {
|
|
136
|
+
// Best-effort: count entries in nx.json `projects` (legacy) or count
|
|
137
|
+
// `project.json` files. Avoid recursing the whole tree; cap depth.
|
|
138
|
+
const nxFile = nodePath.join(projectRoot, 'nx.json');
|
|
139
|
+
try {
|
|
140
|
+
const nx = JSON.parse(readFileSync(nxFile, 'utf8'));
|
|
141
|
+
if (nx.projects && typeof nx.projects === 'object') {
|
|
142
|
+
return Object.keys(nx.projects).length;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// ignore
|
|
147
|
+
}
|
|
148
|
+
// Fallback: look in apps/ + libs/ for project.json sentinels.
|
|
149
|
+
let count = 0;
|
|
150
|
+
for (const root of ['apps', 'libs', 'packages']) {
|
|
151
|
+
const dir = nodePath.join(projectRoot, root);
|
|
152
|
+
if (!existsSync(dir))
|
|
153
|
+
continue;
|
|
154
|
+
try {
|
|
155
|
+
const entries = readJsonChildren(dir);
|
|
156
|
+
count += entries;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// ignore
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return count > 0 ? count : null;
|
|
163
|
+
}
|
|
164
|
+
function countAngularProjects(projectRoot) {
|
|
165
|
+
try {
|
|
166
|
+
const angularJson = JSON.parse(readFileSync(nodePath.join(projectRoot, 'angular.json'), 'utf8'));
|
|
167
|
+
if (angularJson.projects && typeof angularJson.projects === 'object') {
|
|
168
|
+
return Object.keys(angularJson.projects).length;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// ignore
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
function readJsonChildren(dir) {
|
|
177
|
+
let count = 0;
|
|
178
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
179
|
+
if (e.isDirectory()) {
|
|
180
|
+
const projectJson = nodePath.join(dir, e.name, 'project.json');
|
|
181
|
+
if (existsSync(projectJson))
|
|
182
|
+
count += 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return count;
|
|
186
|
+
}
|
|
187
|
+
const CACHE_DIR = '.sharkcraft';
|
|
188
|
+
const CACHE_FILE = 'shape.json';
|
|
189
|
+
export function cacheProjectShape(projectRoot, detection) {
|
|
190
|
+
const dir = nodePath.join(projectRoot, CACHE_DIR);
|
|
191
|
+
if (!existsSync(dir))
|
|
192
|
+
mkdirSync(dir, { recursive: true });
|
|
193
|
+
const file = nodePath.join(dir, CACHE_FILE);
|
|
194
|
+
const entry = {
|
|
195
|
+
schema: 'sharkcraft.shape.v1',
|
|
196
|
+
detection,
|
|
197
|
+
cachedAt: new Date().toISOString(),
|
|
198
|
+
};
|
|
199
|
+
writeFileSync(file, JSON.stringify(entry, null, 2) + '\n', 'utf8');
|
|
200
|
+
return file;
|
|
201
|
+
}
|
|
202
|
+
export function readCachedProjectShape(projectRoot) {
|
|
203
|
+
const file = nodePath.join(projectRoot, CACHE_DIR, CACHE_FILE);
|
|
204
|
+
if (!existsSync(file))
|
|
205
|
+
return null;
|
|
206
|
+
try {
|
|
207
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AppError, type Result } from '@shrkcrft/core';
|
|
2
|
+
export interface ITsConfig {
|
|
3
|
+
target?: string;
|
|
4
|
+
module?: string;
|
|
5
|
+
strict?: boolean;
|
|
6
|
+
paths?: Record<string, string[]>;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
extends?: string;
|
|
9
|
+
raw: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare function readTsConfig(projectRoot: string): Result<ITsConfig | null, AppError>;
|
|
12
|
+
//# sourceMappingURL=tsconfig-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tsconfig-reader.d.ts","sourceRoot":"","sources":["../src/tsconfig-reader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAEhG,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAID,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI,EAAE,QAAQ,CAAC,CAiCpF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
import { AppErrorImpl, ERROR_CODES, err, ok } from '@shrkcrft/core';
|
|
4
|
+
const TSCONFIG_NAMES = ['tsconfig.json', 'tsconfig.base.json'];
|
|
5
|
+
export function readTsConfig(projectRoot) {
|
|
6
|
+
for (const name of TSCONFIG_NAMES) {
|
|
7
|
+
const file = nodePath.join(projectRoot, name);
|
|
8
|
+
if (existsSync(file)) {
|
|
9
|
+
try {
|
|
10
|
+
const text = readFileSync(file, 'utf8');
|
|
11
|
+
// strip // comments and trailing commas to handle JSON-with-comments tsconfigs
|
|
12
|
+
const cleaned = text
|
|
13
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
14
|
+
.replace(/(^|[^:\\])\/\/.*$/gm, '$1')
|
|
15
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
16
|
+
const parsed = JSON.parse(cleaned);
|
|
17
|
+
const compilerOptions = parsed.compilerOptions ?? {};
|
|
18
|
+
return ok({
|
|
19
|
+
target: compilerOptions.target,
|
|
20
|
+
module: compilerOptions.module,
|
|
21
|
+
strict: compilerOptions.strict,
|
|
22
|
+
paths: compilerOptions.paths,
|
|
23
|
+
baseUrl: compilerOptions.baseUrl,
|
|
24
|
+
extends: parsed.extends,
|
|
25
|
+
raw: parsed,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
return err(new AppErrorImpl(ERROR_CODES.FILE_READ_ERROR, `Failed to parse ${name}: ${file}`, {
|
|
30
|
+
details: { file },
|
|
31
|
+
cause: e,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return ok(null);
|
|
37
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IWorkspaceSummary } from './workspace-summary.js';
|
|
2
|
+
export interface InspectWorkspaceOptions {
|
|
3
|
+
startDir?: string;
|
|
4
|
+
sharkcraftDirName?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function inspectWorkspace(options?: InspectWorkspaceOptions): Promise<IWorkspaceSummary>;
|
|
7
|
+
//# sourceMappingURL=workspace-inspector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-inspector.d.ts","sourceRoot":"","sources":["../src/workspace-inspector.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,iBAAiB,CAAC,CA0C5B"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
import { detectProjectRoot } from "./project-root-detector.js";
|
|
4
|
+
import { readPackageJson } from "./package-json-reader.js";
|
|
5
|
+
import { detectPackageManager } from "./package-manager-detector.js";
|
|
6
|
+
import { detectFrameworks } from "./framework-detector.js";
|
|
7
|
+
import { readTsConfig } from "./tsconfig-reader.js";
|
|
8
|
+
import { listTopLevelDirs } from "./folder-scanner.js";
|
|
9
|
+
import { detectProfiles } from "./profile-detector.js";
|
|
10
|
+
export async function inspectWorkspace(options = {}) {
|
|
11
|
+
const startDir = options.startDir ?? process.cwd();
|
|
12
|
+
const sharkcraftDirName = options.sharkcraftDirName ?? 'sharkcraft';
|
|
13
|
+
const { root } = detectProjectRoot(startDir);
|
|
14
|
+
const pkgResult = readPackageJson(root);
|
|
15
|
+
const pkg = pkgResult.ok ? pkgResult.value : null;
|
|
16
|
+
const pkgManager = detectPackageManager(root, pkg);
|
|
17
|
+
const frameworks = detectFrameworks(root, pkg);
|
|
18
|
+
const tsConfigResult = readTsConfig(root);
|
|
19
|
+
const tsConfig = tsConfigResult.ok ? tsConfigResult.value : null;
|
|
20
|
+
const sharkcraftPath = nodePath.join(root, sharkcraftDirName);
|
|
21
|
+
const hasSharkcraftFolder = existsSync(sharkcraftPath) && safeIsDir(sharkcraftPath);
|
|
22
|
+
const topLevelDirs = listTopLevelDirs(root);
|
|
23
|
+
const profileResult = detectProfiles({
|
|
24
|
+
packageJson: pkg,
|
|
25
|
+
frameworks,
|
|
26
|
+
topLevelDirs,
|
|
27
|
+
hasTsConfig: tsConfig !== null,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
projectRoot: root,
|
|
31
|
+
hasPackageJson: pkg !== null,
|
|
32
|
+
packageName: pkg?.name,
|
|
33
|
+
packageVersion: pkg?.version,
|
|
34
|
+
description: pkg?.description,
|
|
35
|
+
packageManager: pkgManager,
|
|
36
|
+
frameworks,
|
|
37
|
+
hasTypeScript: frameworks.some((f) => f.id === 'typescript') || tsConfig !== null,
|
|
38
|
+
tsConfig,
|
|
39
|
+
scripts: pkg?.scripts ?? {},
|
|
40
|
+
dependencies: pkg?.dependencies ?? {},
|
|
41
|
+
devDependencies: pkg?.devDependencies ?? {},
|
|
42
|
+
topLevelDirs,
|
|
43
|
+
hasSharkcraftFolder,
|
|
44
|
+
sharkcraftPath: hasSharkcraftFolder ? sharkcraftPath : null,
|
|
45
|
+
profiles: profileResult.profiles,
|
|
46
|
+
profileEvidence: profileResult.evidence,
|
|
47
|
+
raw: { packageJson: pkg },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function safeIsDir(p) {
|
|
51
|
+
try {
|
|
52
|
+
return statSync(p).isDirectory();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IPackageJson } from './package-json-reader.js';
|
|
2
|
+
import type { IPackageManagerInfo } from './package-manager-detector.js';
|
|
3
|
+
import type { IFrameworkInfo } from './framework-detector.js';
|
|
4
|
+
import type { ITsConfig } from './tsconfig-reader.js';
|
|
5
|
+
import type { IProfileDetectionResult, WorkspaceProfile } from './profile-detector.js';
|
|
6
|
+
export interface IWorkspaceSummary {
|
|
7
|
+
projectRoot: string;
|
|
8
|
+
hasPackageJson: boolean;
|
|
9
|
+
packageName?: string;
|
|
10
|
+
packageVersion?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
packageManager: IPackageManagerInfo;
|
|
13
|
+
frameworks: IFrameworkInfo[];
|
|
14
|
+
hasTypeScript: boolean;
|
|
15
|
+
tsConfig: ITsConfig | null;
|
|
16
|
+
scripts: Record<string, string>;
|
|
17
|
+
dependencies: Record<string, string>;
|
|
18
|
+
devDependencies: Record<string, string>;
|
|
19
|
+
topLevelDirs: string[];
|
|
20
|
+
hasSharkcraftFolder: boolean;
|
|
21
|
+
sharkcraftPath: string | null;
|
|
22
|
+
/** Inferred profile tags (see WorkspaceProfile). */
|
|
23
|
+
profiles: readonly WorkspaceProfile[];
|
|
24
|
+
profileEvidence: IProfileDetectionResult['evidence'];
|
|
25
|
+
raw: {
|
|
26
|
+
packageJson: IPackageJson | null;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=workspace-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-summary.d.ts","sourceRoot":"","sources":["../src/workspace-summary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEvF,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,mBAAmB,CAAC;IACpC,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,oDAAoD;IACpD,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,eAAe,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IACrD,GAAG,EAAE;QAAE,WAAW,EAAE,YAAY,GAAG,IAAI,CAAA;KAAE,CAAC;CAC3C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shrkcrft/workspace",
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
|
+
"description": "SharkCraft workspace inspector: project root, package.json, package manager, frameworks, tsconfig.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SharkCraft contributors",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"bun": "./src/index.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/shrkcrft/sharkcraft.git",
|
|
26
|
+
"directory": "packages/workspace"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/shrkcrft/sharkcraft",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/shrkcrft/sharkcraft/issues"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"sharkcraft",
|
|
34
|
+
"workspace",
|
|
35
|
+
"project-root",
|
|
36
|
+
"frameworks"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"bun": ">=1.1.0",
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@shrkcrft/core": "^0.1.0-alpha.2"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|