@happyvertical/smrt-core 0.51.37 → 0.51.39
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/agents/build-knowledge.md +25 -0
- package/agents/generators.md +27 -0
- package/agents/schema-paths.md +11 -0
- package/dist/consumer-plugin/artifact-publication.d.ts +2 -0
- package/dist/consumer-plugin/artifact-publication.d.ts.map +1 -1
- package/dist/consumer-plugin/artifact-publication.js +15 -1
- package/dist/consumer-plugin/artifact-publication.js.map +1 -1
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +3 -3
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/generators/mcp-runtime-template.d.ts +17 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +27 -19
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp-tool-name.d.ts +17 -0
- package/dist/generators/mcp-tool-name.d.ts.map +1 -0
- package/dist/generators/mcp-tool-name.js +33 -0
- package/dist/generators/mcp-tool-name.js.map +1 -0
- package/dist/generators/mcp.d.ts +46 -1
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +105 -53
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/tool-schema.d.ts +6 -0
- package/dist/generators/tool-schema.d.ts.map +1 -1
- package/dist/generators/tool-schema.js +8 -3
- package/dist/generators/tool-schema.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/knowledge.d.ts +2 -0
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +3 -1
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/discover-base-classes.js +2 -2
- package/dist/manifest/discover-base-classes.js.map +1 -1
- package/dist/manifest/discover-smrt-packages.d.ts.map +1 -1
- package/dist/manifest/discover-smrt-packages.js +3 -2
- package/dist/manifest/discover-smrt-packages.js.map +1 -1
- package/dist/manifest/generator.d.ts +7 -0
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +30 -15
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/manager.d.ts.map +1 -1
- package/dist/manifest/manager.js +6 -2
- package/dist/manifest/manager.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts +2 -0
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +35 -5
- package/dist/migrations/differ.js.map +1 -1
- package/dist/node_modules/.pnpm/{brace-expansion@5.0.9 → brace-expansion@5.0.11}/node_modules/brace-expansion/dist/esm/index.js +32 -19
- package/dist/node_modules/.pnpm/brace-expansion@5.0.11/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.6/node_modules/minimatch/dist/esm/index.js +1 -1
- package/dist/scanner/types.d.ts +2 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/scripts/generate-manifest.js +44 -44
- package/dist/scripts/generate-test-manifest.js +49 -2
- package/dist/smrt-knowledge.json +9 -9
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +16 -6
- package/dist/vite-plugin/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/node_modules/.pnpm/brace-expansion@5.0.9/node_modules/brace-expansion/dist/esm/index.js.map +0 -1
|
@@ -78,7 +78,7 @@ var DEFAULT_BASE_CLASSES = [
|
|
|
78
78
|
async function discoverBaseClasses(options = {}) {
|
|
79
79
|
const { includeDefaults = true, cwd = process.cwd() } = options;
|
|
80
80
|
const baseClasses = includeDefaults ? [...DEFAULT_BASE_CLASSES] : [];
|
|
81
|
-
const smrtDependencies = discoverSmrtPackages();
|
|
81
|
+
const smrtDependencies = discoverSmrtPackages({ baseDir: cwd });
|
|
82
82
|
for (const pkgName of smrtDependencies) {
|
|
83
83
|
const manifestPath = resolveManifestPath(pkgName, cwd);
|
|
84
84
|
if (!manifestPath) {
|
|
@@ -114,7 +114,7 @@ async function discoverBaseClasses(options = {}) {
|
|
|
114
114
|
function discoverBaseClassesSync(options = {}) {
|
|
115
115
|
const { includeDefaults = true, cwd = process.cwd() } = options;
|
|
116
116
|
const baseClasses = includeDefaults ? [...DEFAULT_BASE_CLASSES] : [];
|
|
117
|
-
const smrtDependencies = discoverSmrtPackages();
|
|
117
|
+
const smrtDependencies = discoverSmrtPackages({ baseDir: cwd });
|
|
118
118
|
for (const pkgName of smrtDependencies) {
|
|
119
119
|
const manifestPath = resolveManifestPath(pkgName, cwd);
|
|
120
120
|
if (!manifestPath) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover-base-classes.js","names":[],"sources":["../../src/manifest/discover-base-classes.ts"],"sourcesContent":["/**\n * Discover SMRT classes from external packages for inheritance detection\n *\n * Loads manifests from external SMRT packages and extracts all class names.\n * These are used by the scanner for inheritance detection - to determine\n * if a class extends a valid SMRT class from an external package.\n *\n * IMPORTANT: Despite the name, this function returns ALL SMRT classes from\n * external packages, not just \"base classes\". The two have different uses:\n *\n * 1. DEFAULT_BASE_CLASSES (SmrtObject, SmrtClass, SmrtCollection):\n * Used by the scanner to SKIP these framework classes from the manifest.\n * These are the only classes that should be filtered out.\n *\n * 2. All discovered classes (return value of this function):\n * Used by the scanner for INHERITANCE DETECTION - to recognize when a\n * local class extends an external SMRT class.\n *\n * Issue #847 Fix: The scanner now uses DEFAULT_BASE_CLASSES for skipping,\n * not the full return value of this function. This prevents local classes\n * with names matching external classes from being incorrectly skipped.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { createLogger } from '@happyvertical/logger';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Log a manifest-load failure during base-class discovery without aborting the\n * whole scan. ENOENT (a package that simply hasn't built/published a manifest)\n * is benign and logged at debug; malformed JSON or other read errors are\n * surfaced at warn so a stale/corrupt manifest is not silently ignored.\n */\nfunction logBaseClassLoadError(\n pkgName: string,\n manifestPath: string,\n error: unknown,\n): void {\n const code =\n error && typeof error === 'object' && 'code' in error\n ? (error as { code?: string }).code\n : undefined;\n const message = error instanceof Error ? error.message : String(error);\n\n if (code === 'ENOENT') {\n logger.debug(\n `[discoverBaseClasses] No manifest for ${pkgName} at ${manifestPath}; skipping.`,\n );\n } else {\n logger.warn(\n `[discoverBaseClasses] Failed to load manifest for ${pkgName} at ${manifestPath}: ${message}`,\n );\n }\n}\n\n/**\n * Framework base classes that should be skipped during manifest generation\n *\n * These are the ONLY classes that the scanner should skip - they are\n * framework base classes that should not appear in the manifest themselves.\n *\n * Note: This is separate from inheritance detection. A local class named\n * \"Council\" should NOT be skipped just because an external package has a\n * class named \"Council\" - only these framework classes should be skipped.\n */\nexport const DEFAULT_BASE_CLASSES = [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n] as const;\n\n/**\n * Discover all available base classes for scanning\n *\n * Combines the default SMRT framework base classes with classes discovered\n * from external SMRT package manifests.\n *\n * @param options - Discovery options\n * @param options.includeDefaults - Include default base classes (default: true)\n * @param options.cwd - Current working directory (default: process.cwd())\n * @returns Array of base class names\n *\n * @example\n * ```typescript\n * // Discover all base classes\n * const baseClasses = await discoverBaseClasses();\n * // ['SmrtObject', 'SmrtClass', 'SmrtCollection', 'ProfileRelationship', ...]\n *\n * // Only external classes\n * const external = await discoverBaseClasses({ includeDefaults: false });\n * // ['ProfileRelationship', 'Event', 'Organization', ...]\n * ```\n */\nexport async function discoverBaseClasses(\n options: { includeDefaults?: boolean; cwd?: string } = {},\n): Promise<string[]> {\n const { includeDefaults = true, cwd = process.cwd() } = options;\n\n // Start with default base classes\n const baseClasses: string[] = includeDefaults\n ? [...DEFAULT_BASE_CLASSES]\n : [];\n\n // Discover external SMRT packages\n const smrtDependencies = discoverSmrtPackages();\n\n // Load external base classes from SMRT package manifests.\n // Resolve each manifest the same way discovery does (honoring `.smrt/` and\n // `src/manifest/` in addition to `dist/`), so a workspace package not yet\n // built to dist/ still contributes its base classes (#1378). A hardcoded\n // `node_modules/{pkg}/dist/manifest.json` path silently degraded inheritance\n // detection for source-only packages.\n for (const pkgName of smrtDependencies) {\n const manifestPath = resolveManifestPath(pkgName, cwd);\n if (!manifestPath) {\n logger.debug(\n `[discoverBaseClasses] No SMRT manifest resolved for ${pkgName}; skipping.`,\n );\n continue;\n }\n\n try {\n const manifestContent = await readFile(manifestPath, 'utf-8');\n const manifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n if (manifest.objects && typeof manifest.objects === 'object') {\n for (const objDef of Object.values(manifest.objects)) {\n if (\n objDef &&\n typeof objDef === 'object' &&\n 'className' in objDef &&\n typeof objDef.className === 'string'\n ) {\n baseClasses.push(objDef.className);\n }\n }\n }\n } catch (error) {\n logBaseClassLoadError(pkgName, manifestPath, error);\n }\n }\n\n return baseClasses;\n}\n\n/**\n * Synchronous version of discoverBaseClasses\n *\n * Uses readFileSync to load manifests synchronously. Works in both ESM and CJS.\n *\n * @param options - Discovery options\n * @returns Array of base class names\n *\n * @example\n * ```typescript\n * const baseClasses = discoverBaseClassesSync();\n * const scanner = new OxcScanner({ baseClasses });\n * ```\n */\nexport function discoverBaseClassesSync(\n options: { includeDefaults?: boolean; cwd?: string } = {},\n): string[] {\n const { includeDefaults = true, cwd = process.cwd() } = options;\n\n // Start with default base classes\n const baseClasses: string[] = includeDefaults\n ? [...DEFAULT_BASE_CLASSES]\n : [];\n\n // Discover external SMRT packages\n const smrtDependencies = discoverSmrtPackages();\n\n // Load external base classes from SMRT package manifests. Same resolution as\n // the async variant (honors `.smrt/` and `src/manifest/`, not just `dist/`),\n // so source-only workspace packages still contribute base classes (#1378).\n for (const pkgName of smrtDependencies) {\n const manifestPath = resolveManifestPath(pkgName, cwd);\n if (!manifestPath) {\n logger.debug(\n `[discoverBaseClassesSync] No SMRT manifest resolved for ${pkgName}; skipping.`,\n );\n continue;\n }\n\n try {\n // Use fs.readFileSync for synchronous loading (works in ESM)\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n if (manifest.objects && typeof manifest.objects === 'object') {\n for (const objDef of Object.values(manifest.objects)) {\n if (\n objDef &&\n typeof objDef === 'object' &&\n 'className' in objDef &&\n typeof objDef.className === 'string'\n ) {\n baseClasses.push(objDef.className);\n }\n }\n }\n } catch (error) {\n logBaseClassLoadError(pkgName, manifestPath, error);\n }\n }\n\n return baseClasses;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAM,SAAS,aAAa,EAAE,OAAO,OAAO,CAAC;;;;;;;AAQ7C,SAAS,sBACP,SACA,cACA,OACM;CACN,MAAM,OACJ,SAAS,OAAO,UAAU,YAAY,UAAU,QAC3C,MAA4B,OAC7B,KAAA;CACN,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CAErE,IAAI,SAAS,UACX,OAAO,MACL,yCAAyC,QAAQ,MAAM,aAAa,YACtE;MAEA,OAAO,KACL,qDAAqD,QAAQ,MAAM,aAAa,IAAI,SACtF;AAEJ;;;;;;;;;;;AAYA,IAAa,uBAAuB;CAClC;CACA;CACA;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,eAAsB,oBACpB,UAAuD,CAAC,GACrC;CACnB,MAAM,EAAE,kBAAkB,MAAM,MAAM,QAAQ,IAAI,MAAM;CAGxD,MAAM,cAAwB,kBAC1B,CAAC,GAAG,oBAAoB,IACxB,CAAC;CAGL,MAAM,mBAAmB,qBAAqB;
|
|
1
|
+
{"version":3,"file":"discover-base-classes.js","names":[],"sources":["../../src/manifest/discover-base-classes.ts"],"sourcesContent":["/**\n * Discover SMRT classes from external packages for inheritance detection\n *\n * Loads manifests from external SMRT packages and extracts all class names.\n * These are used by the scanner for inheritance detection - to determine\n * if a class extends a valid SMRT class from an external package.\n *\n * IMPORTANT: Despite the name, this function returns ALL SMRT classes from\n * external packages, not just \"base classes\". The two have different uses:\n *\n * 1. DEFAULT_BASE_CLASSES (SmrtObject, SmrtClass, SmrtCollection):\n * Used by the scanner to SKIP these framework classes from the manifest.\n * These are the only classes that should be filtered out.\n *\n * 2. All discovered classes (return value of this function):\n * Used by the scanner for INHERITANCE DETECTION - to recognize when a\n * local class extends an external SMRT class.\n *\n * Issue #847 Fix: The scanner now uses DEFAULT_BASE_CLASSES for skipping,\n * not the full return value of this function. This prevents local classes\n * with names matching external classes from being incorrectly skipped.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { createLogger } from '@happyvertical/logger';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Log a manifest-load failure during base-class discovery without aborting the\n * whole scan. ENOENT (a package that simply hasn't built/published a manifest)\n * is benign and logged at debug; malformed JSON or other read errors are\n * surfaced at warn so a stale/corrupt manifest is not silently ignored.\n */\nfunction logBaseClassLoadError(\n pkgName: string,\n manifestPath: string,\n error: unknown,\n): void {\n const code =\n error && typeof error === 'object' && 'code' in error\n ? (error as { code?: string }).code\n : undefined;\n const message = error instanceof Error ? error.message : String(error);\n\n if (code === 'ENOENT') {\n logger.debug(\n `[discoverBaseClasses] No manifest for ${pkgName} at ${manifestPath}; skipping.`,\n );\n } else {\n logger.warn(\n `[discoverBaseClasses] Failed to load manifest for ${pkgName} at ${manifestPath}: ${message}`,\n );\n }\n}\n\n/**\n * Framework base classes that should be skipped during manifest generation\n *\n * These are the ONLY classes that the scanner should skip - they are\n * framework base classes that should not appear in the manifest themselves.\n *\n * Note: This is separate from inheritance detection. A local class named\n * \"Council\" should NOT be skipped just because an external package has a\n * class named \"Council\" - only these framework classes should be skipped.\n */\nexport const DEFAULT_BASE_CLASSES = [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n] as const;\n\n/**\n * Discover all available base classes for scanning\n *\n * Combines the default SMRT framework base classes with classes discovered\n * from external SMRT package manifests.\n *\n * @param options - Discovery options\n * @param options.includeDefaults - Include default base classes (default: true)\n * @param options.cwd - Current working directory (default: process.cwd())\n * @returns Array of base class names\n *\n * @example\n * ```typescript\n * // Discover all base classes\n * const baseClasses = await discoverBaseClasses();\n * // ['SmrtObject', 'SmrtClass', 'SmrtCollection', 'ProfileRelationship', ...]\n *\n * // Only external classes\n * const external = await discoverBaseClasses({ includeDefaults: false });\n * // ['ProfileRelationship', 'Event', 'Organization', ...]\n * ```\n */\nexport async function discoverBaseClasses(\n options: { includeDefaults?: boolean; cwd?: string } = {},\n): Promise<string[]> {\n const { includeDefaults = true, cwd = process.cwd() } = options;\n\n // Start with default base classes\n const baseClasses: string[] = includeDefaults\n ? [...DEFAULT_BASE_CLASSES]\n : [];\n\n // Discover external SMRT packages\n const smrtDependencies = discoverSmrtPackages({ baseDir: cwd });\n\n // Load external base classes from SMRT package manifests.\n // Resolve each manifest the same way discovery does (honoring `.smrt/` and\n // `src/manifest/` in addition to `dist/`), so a workspace package not yet\n // built to dist/ still contributes its base classes (#1378). A hardcoded\n // `node_modules/{pkg}/dist/manifest.json` path silently degraded inheritance\n // detection for source-only packages.\n for (const pkgName of smrtDependencies) {\n const manifestPath = resolveManifestPath(pkgName, cwd);\n if (!manifestPath) {\n logger.debug(\n `[discoverBaseClasses] No SMRT manifest resolved for ${pkgName}; skipping.`,\n );\n continue;\n }\n\n try {\n const manifestContent = await readFile(manifestPath, 'utf-8');\n const manifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n if (manifest.objects && typeof manifest.objects === 'object') {\n for (const objDef of Object.values(manifest.objects)) {\n if (\n objDef &&\n typeof objDef === 'object' &&\n 'className' in objDef &&\n typeof objDef.className === 'string'\n ) {\n baseClasses.push(objDef.className);\n }\n }\n }\n } catch (error) {\n logBaseClassLoadError(pkgName, manifestPath, error);\n }\n }\n\n return baseClasses;\n}\n\n/**\n * Synchronous version of discoverBaseClasses\n *\n * Uses readFileSync to load manifests synchronously. Works in both ESM and CJS.\n *\n * @param options - Discovery options\n * @returns Array of base class names\n *\n * @example\n * ```typescript\n * const baseClasses = discoverBaseClassesSync();\n * const scanner = new OxcScanner({ baseClasses });\n * ```\n */\nexport function discoverBaseClassesSync(\n options: { includeDefaults?: boolean; cwd?: string } = {},\n): string[] {\n const { includeDefaults = true, cwd = process.cwd() } = options;\n\n // Start with default base classes\n const baseClasses: string[] = includeDefaults\n ? [...DEFAULT_BASE_CLASSES]\n : [];\n\n // Discover external SMRT packages\n const smrtDependencies = discoverSmrtPackages({ baseDir: cwd });\n\n // Load external base classes from SMRT package manifests. Same resolution as\n // the async variant (honors `.smrt/` and `src/manifest/`, not just `dist/`),\n // so source-only workspace packages still contribute base classes (#1378).\n for (const pkgName of smrtDependencies) {\n const manifestPath = resolveManifestPath(pkgName, cwd);\n if (!manifestPath) {\n logger.debug(\n `[discoverBaseClassesSync] No SMRT manifest resolved for ${pkgName}; skipping.`,\n );\n continue;\n }\n\n try {\n // Use fs.readFileSync for synchronous loading (works in ESM)\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n if (manifest.objects && typeof manifest.objects === 'object') {\n for (const objDef of Object.values(manifest.objects)) {\n if (\n objDef &&\n typeof objDef === 'object' &&\n 'className' in objDef &&\n typeof objDef.className === 'string'\n ) {\n baseClasses.push(objDef.className);\n }\n }\n }\n } catch (error) {\n logBaseClassLoadError(pkgName, manifestPath, error);\n }\n }\n\n return baseClasses;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAM,SAAS,aAAa,EAAE,OAAO,OAAO,CAAC;;;;;;;AAQ7C,SAAS,sBACP,SACA,cACA,OACM;CACN,MAAM,OACJ,SAAS,OAAO,UAAU,YAAY,UAAU,QAC3C,MAA4B,OAC7B,KAAA;CACN,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CAErE,IAAI,SAAS,UACX,OAAO,MACL,yCAAyC,QAAQ,MAAM,aAAa,YACtE;MAEA,OAAO,KACL,qDAAqD,QAAQ,MAAM,aAAa,IAAI,SACtF;AAEJ;;;;;;;;;;;AAYA,IAAa,uBAAuB;CAClC;CACA;CACA;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,eAAsB,oBACpB,UAAuD,CAAC,GACrC;CACnB,MAAM,EAAE,kBAAkB,MAAM,MAAM,QAAQ,IAAI,MAAM;CAGxD,MAAM,cAAwB,kBAC1B,CAAC,GAAG,oBAAoB,IACxB,CAAC;CAGL,MAAM,mBAAmB,qBAAqB,EAAE,SAAS,IAAI,CAAC;CAQ9D,KAAK,MAAM,WAAW,kBAAkB;EACtC,MAAM,eAAe,oBAAoB,SAAS,GAAG;EACrD,IAAI,CAAC,cAAc;GACjB,OAAO,MACL,uDAAuD,QAAQ,YACjE;GACA;EACF;EAEA,IAAI;GACF,MAAM,kBAAkB,MAAM,SAAS,cAAc,OAAO;GAC5D,MAAM,WAAW,KAAK,MAAM,eAAe;GAG3C,IAAI,SAAS,WAAW,OAAO,SAAS,YAAY;SAC7C,MAAM,UAAU,OAAO,OAAO,SAAS,OAAO,GACjD,IACE,UACA,OAAO,WAAW,YAClB,eAAe,UACf,OAAO,OAAO,cAAc,UAE5B,YAAY,KAAK,OAAO,SAAS;GAAA;EAIzC,SAAS,OAAO;GACd,sBAAsB,SAAS,cAAc,KAAK;EACpD;CACF;CAEA,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,SAAgB,wBACd,UAAuD,CAAC,GAC9C;CACV,MAAM,EAAE,kBAAkB,MAAM,MAAM,QAAQ,IAAI,MAAM;CAGxD,MAAM,cAAwB,kBAC1B,CAAC,GAAG,oBAAoB,IACxB,CAAC;CAGL,MAAM,mBAAmB,qBAAqB,EAAE,SAAS,IAAI,CAAC;CAK9D,KAAK,MAAM,WAAW,kBAAkB;EACtC,MAAM,eAAe,oBAAoB,SAAS,GAAG;EACrD,IAAI,CAAC,cAAc;GACjB,OAAO,MACL,2DAA2D,QAAQ,YACrE;GACA;EACF;EAEA,IAAI;GAEF,MAAM,kBAAkB,aAAa,cAAc,OAAO;GAC1D,MAAM,WAAW,KAAK,MAAM,eAAe;GAG3C,IAAI,SAAS,WAAW,OAAO,SAAS,YAAY;SAC7C,MAAM,UAAU,OAAO,OAAO,SAAS,OAAO,GACjD,IACE,UACA,OAAO,WAAW,YAClB,eAAe,UACf,OAAO,OAAO,cAAc,UAE5B,YAAY,KAAK,OAAO,SAAS;GAAA;EAIzC,SAAS,OAAO;GACd,sBAAsB,SAAS,cAAc,KAAK;EACpD;CACF;CAEA,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover-smrt-packages.d.ts","sourceRoot":"","sources":["../../src/manifest/discover-smrt-packages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA8BH,oCAAoC;AACpC,UAAU,UAAU;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,UAAU,CAE/C;AAgJD,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,MAAsB,GAC9B,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"discover-smrt-packages.d.ts","sourceRoot":"","sources":["../../src/manifest/discover-smrt-packages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA8BH,oCAAoC;AACpC,UAAU,UAAU;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,UAAU,CAE/C;AAgJD,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,MAAsB,GAC9B,MAAM,GAAG,IAAI,CA2Bf;AAkPD,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0BAA0B;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yBAAyB;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,EAAE,CAsE7E"}
|
|
@@ -32,7 +32,7 @@ var CACHE_FILE = "discovery-cache.json";
|
|
|
32
32
|
* and never re-probe a package whose manifest is only at, say,
|
|
33
33
|
* `dist/lib/manifest.json`.
|
|
34
34
|
*/
|
|
35
|
-
var CACHE_VERSION =
|
|
35
|
+
var CACHE_VERSION = 6;
|
|
36
36
|
/** Module-level timing storage */
|
|
37
37
|
var lastTimingData = {};
|
|
38
38
|
/**
|
|
@@ -125,7 +125,8 @@ function resolveManifestPath(packageName, baseDir = process.cwd()) {
|
|
|
125
125
|
const pkgPath = resolvePackageDir(packageName, baseDir);
|
|
126
126
|
if (!pkgPath) return null;
|
|
127
127
|
for (const manifestPath of manifestPathCandidates(pkgPath)) try {
|
|
128
|
-
|
|
128
|
+
const manifest = parse(readFileSync(manifestPath, "utf-8"));
|
|
129
|
+
if (manifest.moduleType === "smrt" && manifest.artifactPurpose !== "test") return manifestPath;
|
|
129
130
|
} catch {}
|
|
130
131
|
return null;
|
|
131
132
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover-smrt-packages.js","names":[],"sources":["../../src/manifest/discover-smrt-packages.ts"],"sourcesContent":["/**\n * SMRT Package Discovery\n *\n * Discovers all SMRT packages in node_modules by:\n * 1. Scanning node_modules directory for installed packages\n * 2. Following symlinks for workspace: dependencies\n * 3. Checking for a package manifest — the location declared by the package's\n * own `package.json#exports` map, else `dist/manifest.json`,\n * `.smrt/manifest.json`, or `src/manifest/manifest.json` — with moduleType: \"smrt\"\n * 4. Caching results based on lockfile hash and manifest timestamps\n *\n * Cache Strategy:\n * - ENABLED by default (5-50x faster startup)\n * - Automatically invalidates when lockfile or any manifest.json changes\n * - Disable with SMRT_DISABLE_DISCOVERY_CACHE=true for debugging\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n statSync,\n writeFileSync,\n} from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { parse } from '../utils/json.js';\nimport { manifestExportCandidates } from './package-manifest-exports.js';\n\nconst CACHE_DIR = '.smrt';\nconst CACHE_FILE = 'discovery-cache.json';\n/**\n * Bump whenever what counts as a discoverable package changes, not only when\n * the cache file's shape changes. Version 5 adds export-map manifest\n * resolution (#2923): a version-4 cache can hold an EMPTY package list, and\n * `getCachedDiscovery()` skips manifest rehashing in that case, so without\n * this bump a project would keep replaying the pre-fix \"no packages\" answer\n * and never re-probe a package whose manifest is only at, say,\n * `dist/lib/manifest.json`.\n */\nconst CACHE_VERSION = 5;\n\n/** Timing data for --timing flag */\ninterface TimingData {\n discovery?: number;\n cacheCheck?: number;\n total?: number;\n}\n\n/** Module-level timing storage */\nlet lastTimingData: TimingData = {};\n\n/**\n * Get timing data from last discovery operation\n */\nexport function getDiscoveryTiming(): TimingData {\n return { ...lastTimingData };\n}\n\n/**\n * Get hash of lockfile for cache invalidation\n */\nfunction getLockfileHash(baseDir: string): string | null {\n // Check for pnpm or npm lockfile\n const lockfile = existsSync(join(baseDir, 'pnpm-lock.yaml'))\n ? join(baseDir, 'pnpm-lock.yaml')\n : join(baseDir, 'package-lock.json');\n\n if (!existsSync(lockfile)) {\n return null;\n }\n\n const content = readFileSync(lockfile, 'utf-8');\n return createHash('sha256').update(content).digest('hex');\n}\n\n/**\n * Get hash of package.json for cache invalidation when a package workspace\n * changes its declared dependencies without a colocated lockfile.\n */\nfunction getPackageJsonHash(baseDir: string): string | null {\n const packageJsonPath = join(baseDir, 'package.json');\n\n if (!existsSync(packageJsonPath)) {\n return null;\n }\n\n const content = readFileSync(packageJsonPath, 'utf-8');\n return createHash('sha256').update(content).digest('hex');\n}\n\n/**\n * Get a hash of all manifest timestamps for cache invalidation\n * This catches changes to SMRT packages even when lockfile hasn't changed\n */\nfunction getManifestTimestampsHash(\n baseDir: string,\n packages: string[],\n): string {\n const timestamps: string[] = [];\n\n for (const pkgName of packages) {\n try {\n const manifestPath = resolveManifestPath(pkgName, baseDir);\n\n if (manifestPath && existsSync(manifestPath)) {\n const stats = statSync(manifestPath);\n timestamps.push(`${pkgName}:${stats.mtimeMs}`);\n }\n } catch {\n // Ignore errors, package may have been removed\n }\n }\n\n return createHash('sha256').update(timestamps.join('|')).digest('hex');\n}\n\ninterface DiscoveryCache {\n version: number;\n lockfileHash: string | null;\n packageJsonHash: string | null;\n manifestsHash: string;\n timestamp: number;\n packages: string[];\n}\n\n/**\n * Every manifest file that exists for a package, in preference order.\n *\n * This returns ALL of them rather than just the first, because existing is not\n * the same as being a manifest. An export target can exist and still be\n * unusable here: `./manifest` is commonly a JS module (`@happyvertical/smrt-core`\n * itself maps it to `dist/manifest.js`) and `./static-manifest` always is, and\n * an exported JSON file can be stale or malformed. Stopping at the first\n * existing path would let any of those hide a perfectly good\n * `dist/manifest.json` and drop the package silently — the very #2923 failure\n * mode this file is fixing.\n */\nfunction manifestPathCandidates(pkgPath: string): string[] {\n return [\n ...manifestExportCandidates(pkgPath),\n join(pkgPath, 'dist', 'manifest.json'),\n join(pkgPath, '.smrt', 'manifest.json'),\n join(pkgPath, 'src', 'manifest', 'manifest.json'),\n ].filter((candidate) => existsSync(candidate));\n}\n\nfunction normalizePackagePath(pkgPath: string): string {\n try {\n return realpathSync(pkgPath);\n } catch {\n return pkgPath;\n }\n}\n\nfunction resolvePackageDir(\n packageName: string,\n baseDir: string = process.cwd(),\n): string | null {\n try {\n const requireFromBase = createRequire(join(baseDir, 'package.json'));\n const packageEntry = requireFromBase.resolve(packageName);\n let currentDir = dirname(packageEntry);\n let resolvedPackageDir: string | null = null;\n\n for (let i = 0; i < 10; i++) {\n const packageJsonPath = join(currentDir, 'package.json');\n\n if (existsSync(packageJsonPath)) {\n const packageJson = parse<{ name?: string }>(\n readFileSync(packageJsonPath, 'utf-8'),\n );\n\n if (packageJson.name === packageName) {\n resolvedPackageDir = currentDir;\n }\n }\n\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) {\n break;\n }\n\n currentDir = parentDir;\n }\n\n if (resolvedPackageDir) {\n return normalizePackagePath(resolvedPackageDir);\n }\n } catch {\n // Fall through to direct node_modules lookup below.\n }\n\n const directPath = join(baseDir, 'node_modules', packageName);\n if (existsSync(directPath)) {\n return normalizePackagePath(directPath);\n }\n\n return null;\n}\n\nexport function resolveManifestPath(\n packageName: string,\n baseDir: string = process.cwd(),\n): string | null {\n const pkgPath = resolvePackageDir(packageName, baseDir);\n if (!pkgPath) {\n return null;\n }\n\n // Take the first candidate that actually parses as a SMRT manifest. A\n // candidate that exists but is a JS module, stale, or malformed must not end\n // the search, or it would mask a valid conventional manifest behind it.\n for (const manifestPath of manifestPathCandidates(pkgPath)) {\n try {\n const manifest = parse<{ moduleType?: string }>(\n readFileSync(manifestPath, 'utf-8'),\n );\n\n if (manifest.moduleType === 'smrt') {\n return manifestPath;\n }\n } catch {\n // Not a readable JSON manifest; try the next candidate.\n }\n }\n\n return null;\n}\n\n/**\n * Load cached discovery results if valid\n * Cache is invalidated if:\n * - Lockfile hash changed (dependencies changed)\n * - Manifest timestamps hash changed (SMRT packages rebuilt)\n */\nfunction getCachedDiscovery(\n baseDir: string,\n verbose: boolean,\n): { packages: string[]; reason?: string } | null {\n const cachePath = join(baseDir, CACHE_DIR, CACHE_FILE);\n\n if (!existsSync(cachePath)) {\n return null;\n }\n\n try {\n const cache: DiscoveryCache = parse(readFileSync(cachePath, 'utf-8'));\n if (cache.version !== CACHE_VERSION) {\n if (verbose) {\n console.log('[discovery] Cache invalid: discovery version changed');\n }\n return null;\n }\n\n const currentLockfileHash = getLockfileHash(baseDir);\n const currentPackageJsonHash = getPackageJsonHash(baseDir);\n\n // Check lockfile hash\n if (cache.lockfileHash !== currentLockfileHash) {\n if (verbose) {\n console.log('[discovery] Cache invalid: lockfile changed');\n }\n return null;\n }\n\n if (cache.packageJsonHash !== currentPackageJsonHash) {\n if (verbose) {\n console.log('[discovery] Cache invalid: package.json changed');\n }\n return null;\n }\n\n // Check manifest timestamps (only if we have cached packages)\n if (cache.packages.length > 0) {\n const currentManifestsHash = getManifestTimestampsHash(\n baseDir,\n cache.packages,\n );\n if (cache.manifestsHash !== currentManifestsHash) {\n if (verbose) {\n console.log('[discovery] Cache invalid: manifest(s) changed');\n }\n return null;\n }\n }\n\n return { packages: cache.packages };\n } catch (error) {\n if (verbose) {\n console.warn(\n '[discovery] Failed to read cache:',\n (error as Error).message,\n );\n }\n return null;\n }\n}\n\n/**\n * Save discovery results to cache\n */\nfunction saveCachedDiscovery(\n baseDir: string,\n packages: string[],\n verbose: boolean,\n): void {\n const cache: DiscoveryCache = {\n version: CACHE_VERSION,\n lockfileHash: getLockfileHash(baseDir),\n packageJsonHash: getPackageJsonHash(baseDir),\n manifestsHash: getManifestTimestampsHash(baseDir, packages),\n timestamp: Date.now(),\n packages: packages,\n };\n\n try {\n mkdirSync(join(baseDir, CACHE_DIR), { recursive: true });\n writeFileSync(\n join(baseDir, CACHE_DIR, CACHE_FILE),\n JSON.stringify(cache, null, 2),\n );\n if (verbose) {\n console.log(`[discovery] Saved cache with ${packages.length} package(s)`);\n }\n } catch (error) {\n if (verbose) {\n console.warn(\n '[discovery] Failed to save cache:',\n (error as Error).message,\n );\n }\n }\n}\n\n/**\n * Check if a package provides a SMRT manifest\n *\n * Supports both regular npm dependencies and workspace: symlinks\n */\nfunction hasManifestExport(packageName: string, baseDir: string): boolean {\n return resolveManifestPath(packageName, baseDir) !== null;\n}\n\nfunction getDeclaredSmrtPackages(baseDir: string, verbose: boolean): string[] {\n const packageJsonPath = join(baseDir, 'package.json');\n if (!existsSync(packageJsonPath)) {\n return [];\n }\n\n try {\n const packageJson = parse<{\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n }>(readFileSync(packageJsonPath, 'utf-8'));\n\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n ...packageJson.peerDependencies,\n };\n\n return Object.keys(allDeps).filter((pkgName) => {\n if (!pkgName.startsWith('@happyvertical/smrt-')) {\n return false;\n }\n\n const hasManifest = hasManifestExport(pkgName, baseDir);\n if (verbose && hasManifest) {\n console.log(`[discovery] ✅ Found declared SMRT package: ${pkgName}`);\n }\n\n return hasManifest;\n });\n } catch (error) {\n if (verbose) {\n console.warn(\n '[discovery] Failed to read declared dependencies:',\n (error as Error).message,\n );\n }\n return [];\n }\n}\n\n/**\n * Scan node_modules recursively for packages\n */\nfunction* scanNodeModules(baseDir: string): Generator<string> {\n const nodeModulesPath = join(baseDir, 'node_modules');\n\n if (!existsSync(nodeModulesPath)) {\n return;\n }\n\n try {\n const entries = readdirSync(nodeModulesPath);\n\n for (const entry of entries) {\n if (entry === '.bin' || entry === '.pnpm' || entry === '.cache') {\n continue;\n }\n\n const entryPath = join(nodeModulesPath, entry);\n\n try {\n const stats = statSync(entryPath);\n\n if (stats.isDirectory() || stats.isSymbolicLink()) {\n // Scoped packages (e.g., @happyvertical/smrt-core)\n if (entry.startsWith('@')) {\n const scopeEntries = readdirSync(entryPath);\n for (const scopedPkg of scopeEntries) {\n yield `${entry}/${scopedPkg}`;\n }\n } else {\n // Regular packages\n yield entry;\n }\n }\n } catch {}\n }\n } catch (error) {\n // node_modules doesn't exist or can't be read\n return;\n }\n}\n\n/**\n * Perform fresh discovery of SMRT packages\n */\nfunction performDiscovery(baseDir: string, verbose: boolean): string[] {\n if (verbose) {\n console.log('[discovery] Scanning node_modules for SMRT packages...');\n }\n\n try {\n const smrtPackages = new Set<string>();\n\n // Scan node_modules for all packages\n for (const pkgName of scanNodeModules(baseDir)) {\n if (hasManifestExport(pkgName, baseDir)) {\n smrtPackages.add(pkgName);\n if (verbose) {\n console.log(`[discovery] ✅ Found SMRT package: ${pkgName}`);\n }\n }\n }\n\n for (const pkgName of getDeclaredSmrtPackages(baseDir, verbose)) {\n smrtPackages.add(pkgName);\n }\n\n if (verbose) {\n console.log(\n `[discovery] Discovered ${smrtPackages.size} SMRT package(s)`,\n );\n }\n\n return Array.from(smrtPackages);\n } catch (error) {\n console.error(\n '[discovery] Failed to discover packages:',\n (error as Error).message,\n );\n return [];\n }\n}\n\nexport interface DiscoveryOptions {\n /** Override project root for discovery/cache */\n baseDir?: string;\n /** Force fresh discovery, ignoring cache */\n noCache?: boolean;\n /** Show verbose output */\n verbose?: boolean;\n /** Record timing data */\n timing?: boolean;\n}\n\n/**\n * Main discovery function\n *\n * Cache ENABLED by default (5-50x faster startup)\n * - Automatically invalidates when lockfile changes (dependencies updated)\n * - Automatically invalidates when any manifest.json changes (packages rebuilt)\n *\n * Disable with SMRT_DISABLE_DISCOVERY_CACHE=true for debugging\n *\n * Intentional split (#1579): this is the **build-time** discovery path —\n * synchronous, scans `node_modules` for `manifest.json` files with\n * `moduleType: \"smrt\"`, and caches by lockfile/manifest hash for fast manifest\n * generation. It is deliberately distinct from the consumer-plugin's\n * `discoverSmrtPackages(projectRoot)` (`src/consumer-plugin/index.ts`), which is\n * **async**, reads a downstream app's `package.json` dependency names with a\n * lightweight `@have/`/`smrt` heuristic, and runs inside the Vite consumer\n * plugin. Different inputs, contexts, and lifecycles — not duplicated logic to\n * consolidate.\n */\nexport function discoverSmrtPackages(options: DiscoveryOptions = {}): string[] {\n const startTime = options.timing ? performance.now() : 0;\n lastTimingData = {};\n const baseDir = options.baseDir || process.cwd();\n\n const cacheDisabled =\n options.noCache || process.env.SMRT_DISABLE_DISCOVERY_CACHE === 'true';\n\n const verbose: boolean =\n options.verbose === true ||\n process.env.SMRT_VERBOSE === 'true' ||\n !!process.env.DEBUG?.includes('smrt');\n\n if (cacheDisabled) {\n if (verbose) {\n console.log('[discovery] Cache disabled, performing fresh discovery...');\n }\n\n const packages = performDiscovery(baseDir, verbose);\n\n if (options.timing) {\n lastTimingData.discovery = performance.now() - startTime;\n lastTimingData.total = lastTimingData.discovery;\n }\n\n return packages;\n }\n\n // Try cache first\n const cacheCheckStart = options.timing ? performance.now() : 0;\n const cached = getCachedDiscovery(baseDir, verbose);\n\n if (options.timing) {\n lastTimingData.cacheCheck = performance.now() - cacheCheckStart;\n }\n\n if (cached) {\n if (verbose) {\n console.log(\n `[discovery] ✅ Using cached SMRT packages (${cached.packages.length} package(s))`,\n );\n }\n\n if (options.timing) {\n lastTimingData.total = performance.now() - startTime;\n }\n\n return cached.packages;\n }\n\n // No valid cache - perform fresh discovery\n if (verbose) {\n console.log('[discovery] Cache miss, performing discovery...');\n }\n\n const discoveryStart = options.timing ? performance.now() : 0;\n const packages = performDiscovery(baseDir, verbose);\n\n if (options.timing) {\n lastTimingData.discovery = performance.now() - discoveryStart;\n }\n\n // Save to cache\n saveCachedDiscovery(baseDir, packages, verbose);\n\n if (options.timing) {\n lastTimingData.total = performance.now() - startTime;\n }\n\n return packages;\n}\n\n// Run if called directly\nif (import.meta.url === `file://${process.argv[1]}`) {\n const noCache = process.argv.includes('--no-cache');\n const verbose =\n process.argv.includes('--verbose') || process.argv.includes('-v');\n const timing = process.argv.includes('--timing');\n\n const packages = discoverSmrtPackages({ noCache, verbose, timing });\n\n console.log('\\nDiscovered SMRT packages:');\n console.log(JSON.stringify(packages, null, 2));\n\n if (timing) {\n const timingData = getDiscoveryTiming();\n console.log('\\nTiming:');\n if (timingData.cacheCheck !== undefined) {\n console.log(` Cache check: ${timingData.cacheCheck.toFixed(2)}ms`);\n }\n if (timingData.discovery !== undefined) {\n console.log(` Discovery: ${timingData.discovery.toFixed(2)}ms`);\n }\n if (timingData.total !== undefined) {\n console.log(` Total: ${timingData.total.toFixed(2)}ms`);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"discover-smrt-packages.js","names":[],"sources":["../../src/manifest/discover-smrt-packages.ts"],"sourcesContent":["/**\n * SMRT Package Discovery\n *\n * Discovers all SMRT packages in node_modules by:\n * 1. Scanning node_modules directory for installed packages\n * 2. Following symlinks for workspace: dependencies\n * 3. Checking for a package manifest — the location declared by the package's\n * own `package.json#exports` map, else `dist/manifest.json`,\n * `.smrt/manifest.json`, or `src/manifest/manifest.json` — with moduleType: \"smrt\"\n * 4. Caching results based on lockfile hash and manifest timestamps\n *\n * Cache Strategy:\n * - ENABLED by default (5-50x faster startup)\n * - Automatically invalidates when lockfile or any manifest.json changes\n * - Disable with SMRT_DISABLE_DISCOVERY_CACHE=true for debugging\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n statSync,\n writeFileSync,\n} from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { parse } from '../utils/json.js';\nimport { manifestExportCandidates } from './package-manifest-exports.js';\n\nconst CACHE_DIR = '.smrt';\nconst CACHE_FILE = 'discovery-cache.json';\n/**\n * Bump whenever what counts as a discoverable package changes, not only when\n * the cache file's shape changes. Version 5 adds export-map manifest\n * resolution (#2923): a version-4 cache can hold an EMPTY package list, and\n * `getCachedDiscovery()` skips manifest rehashing in that case, so without\n * this bump a project would keep replaying the pre-fix \"no packages\" answer\n * and never re-probe a package whose manifest is only at, say,\n * `dist/lib/manifest.json`.\n */\nconst CACHE_VERSION = 6;\n\n/** Timing data for --timing flag */\ninterface TimingData {\n discovery?: number;\n cacheCheck?: number;\n total?: number;\n}\n\n/** Module-level timing storage */\nlet lastTimingData: TimingData = {};\n\n/**\n * Get timing data from last discovery operation\n */\nexport function getDiscoveryTiming(): TimingData {\n return { ...lastTimingData };\n}\n\n/**\n * Get hash of lockfile for cache invalidation\n */\nfunction getLockfileHash(baseDir: string): string | null {\n // Check for pnpm or npm lockfile\n const lockfile = existsSync(join(baseDir, 'pnpm-lock.yaml'))\n ? join(baseDir, 'pnpm-lock.yaml')\n : join(baseDir, 'package-lock.json');\n\n if (!existsSync(lockfile)) {\n return null;\n }\n\n const content = readFileSync(lockfile, 'utf-8');\n return createHash('sha256').update(content).digest('hex');\n}\n\n/**\n * Get hash of package.json for cache invalidation when a package workspace\n * changes its declared dependencies without a colocated lockfile.\n */\nfunction getPackageJsonHash(baseDir: string): string | null {\n const packageJsonPath = join(baseDir, 'package.json');\n\n if (!existsSync(packageJsonPath)) {\n return null;\n }\n\n const content = readFileSync(packageJsonPath, 'utf-8');\n return createHash('sha256').update(content).digest('hex');\n}\n\n/**\n * Get a hash of all manifest timestamps for cache invalidation\n * This catches changes to SMRT packages even when lockfile hasn't changed\n */\nfunction getManifestTimestampsHash(\n baseDir: string,\n packages: string[],\n): string {\n const timestamps: string[] = [];\n\n for (const pkgName of packages) {\n try {\n const manifestPath = resolveManifestPath(pkgName, baseDir);\n\n if (manifestPath && existsSync(manifestPath)) {\n const stats = statSync(manifestPath);\n timestamps.push(`${pkgName}:${stats.mtimeMs}`);\n }\n } catch {\n // Ignore errors, package may have been removed\n }\n }\n\n return createHash('sha256').update(timestamps.join('|')).digest('hex');\n}\n\ninterface DiscoveryCache {\n version: number;\n lockfileHash: string | null;\n packageJsonHash: string | null;\n manifestsHash: string;\n timestamp: number;\n packages: string[];\n}\n\n/**\n * Every manifest file that exists for a package, in preference order.\n *\n * This returns ALL of them rather than just the first, because existing is not\n * the same as being a manifest. An export target can exist and still be\n * unusable here: `./manifest` is commonly a JS module (`@happyvertical/smrt-core`\n * itself maps it to `dist/manifest.js`) and `./static-manifest` always is, and\n * an exported JSON file can be stale or malformed. Stopping at the first\n * existing path would let any of those hide a perfectly good\n * `dist/manifest.json` and drop the package silently — the very #2923 failure\n * mode this file is fixing.\n */\nfunction manifestPathCandidates(pkgPath: string): string[] {\n return [\n ...manifestExportCandidates(pkgPath),\n join(pkgPath, 'dist', 'manifest.json'),\n join(pkgPath, '.smrt', 'manifest.json'),\n join(pkgPath, 'src', 'manifest', 'manifest.json'),\n ].filter((candidate) => existsSync(candidate));\n}\n\nfunction normalizePackagePath(pkgPath: string): string {\n try {\n return realpathSync(pkgPath);\n } catch {\n return pkgPath;\n }\n}\n\nfunction resolvePackageDir(\n packageName: string,\n baseDir: string = process.cwd(),\n): string | null {\n try {\n const requireFromBase = createRequire(join(baseDir, 'package.json'));\n const packageEntry = requireFromBase.resolve(packageName);\n let currentDir = dirname(packageEntry);\n let resolvedPackageDir: string | null = null;\n\n for (let i = 0; i < 10; i++) {\n const packageJsonPath = join(currentDir, 'package.json');\n\n if (existsSync(packageJsonPath)) {\n const packageJson = parse<{ name?: string }>(\n readFileSync(packageJsonPath, 'utf-8'),\n );\n\n if (packageJson.name === packageName) {\n resolvedPackageDir = currentDir;\n }\n }\n\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) {\n break;\n }\n\n currentDir = parentDir;\n }\n\n if (resolvedPackageDir) {\n return normalizePackagePath(resolvedPackageDir);\n }\n } catch {\n // Fall through to direct node_modules lookup below.\n }\n\n const directPath = join(baseDir, 'node_modules', packageName);\n if (existsSync(directPath)) {\n return normalizePackagePath(directPath);\n }\n\n return null;\n}\n\nexport function resolveManifestPath(\n packageName: string,\n baseDir: string = process.cwd(),\n): string | null {\n const pkgPath = resolvePackageDir(packageName, baseDir);\n if (!pkgPath) {\n return null;\n }\n\n // Take the first candidate that actually parses as a SMRT manifest. A\n // candidate that exists but is a JS module, stale, or malformed must not end\n // the search, or it would mask a valid conventional manifest behind it.\n for (const manifestPath of manifestPathCandidates(pkgPath)) {\n try {\n const manifest = parse<{ moduleType?: string; artifactPurpose?: string }>(\n readFileSync(manifestPath, 'utf-8'),\n );\n\n if (\n manifest.moduleType === 'smrt' &&\n manifest.artifactPurpose !== 'test'\n ) {\n return manifestPath;\n }\n } catch {\n // Not a readable JSON manifest; try the next candidate.\n }\n }\n\n return null;\n}\n\n/**\n * Load cached discovery results if valid\n * Cache is invalidated if:\n * - Lockfile hash changed (dependencies changed)\n * - Manifest timestamps hash changed (SMRT packages rebuilt)\n */\nfunction getCachedDiscovery(\n baseDir: string,\n verbose: boolean,\n): { packages: string[]; reason?: string } | null {\n const cachePath = join(baseDir, CACHE_DIR, CACHE_FILE);\n\n if (!existsSync(cachePath)) {\n return null;\n }\n\n try {\n const cache: DiscoveryCache = parse(readFileSync(cachePath, 'utf-8'));\n if (cache.version !== CACHE_VERSION) {\n if (verbose) {\n console.log('[discovery] Cache invalid: discovery version changed');\n }\n return null;\n }\n\n const currentLockfileHash = getLockfileHash(baseDir);\n const currentPackageJsonHash = getPackageJsonHash(baseDir);\n\n // Check lockfile hash\n if (cache.lockfileHash !== currentLockfileHash) {\n if (verbose) {\n console.log('[discovery] Cache invalid: lockfile changed');\n }\n return null;\n }\n\n if (cache.packageJsonHash !== currentPackageJsonHash) {\n if (verbose) {\n console.log('[discovery] Cache invalid: package.json changed');\n }\n return null;\n }\n\n // Check manifest timestamps (only if we have cached packages)\n if (cache.packages.length > 0) {\n const currentManifestsHash = getManifestTimestampsHash(\n baseDir,\n cache.packages,\n );\n if (cache.manifestsHash !== currentManifestsHash) {\n if (verbose) {\n console.log('[discovery] Cache invalid: manifest(s) changed');\n }\n return null;\n }\n }\n\n return { packages: cache.packages };\n } catch (error) {\n if (verbose) {\n console.warn(\n '[discovery] Failed to read cache:',\n (error as Error).message,\n );\n }\n return null;\n }\n}\n\n/**\n * Save discovery results to cache\n */\nfunction saveCachedDiscovery(\n baseDir: string,\n packages: string[],\n verbose: boolean,\n): void {\n const cache: DiscoveryCache = {\n version: CACHE_VERSION,\n lockfileHash: getLockfileHash(baseDir),\n packageJsonHash: getPackageJsonHash(baseDir),\n manifestsHash: getManifestTimestampsHash(baseDir, packages),\n timestamp: Date.now(),\n packages: packages,\n };\n\n try {\n mkdirSync(join(baseDir, CACHE_DIR), { recursive: true });\n writeFileSync(\n join(baseDir, CACHE_DIR, CACHE_FILE),\n JSON.stringify(cache, null, 2),\n );\n if (verbose) {\n console.log(`[discovery] Saved cache with ${packages.length} package(s)`);\n }\n } catch (error) {\n if (verbose) {\n console.warn(\n '[discovery] Failed to save cache:',\n (error as Error).message,\n );\n }\n }\n}\n\n/**\n * Check if a package provides a SMRT manifest\n *\n * Supports both regular npm dependencies and workspace: symlinks\n */\nfunction hasManifestExport(packageName: string, baseDir: string): boolean {\n return resolveManifestPath(packageName, baseDir) !== null;\n}\n\nfunction getDeclaredSmrtPackages(baseDir: string, verbose: boolean): string[] {\n const packageJsonPath = join(baseDir, 'package.json');\n if (!existsSync(packageJsonPath)) {\n return [];\n }\n\n try {\n const packageJson = parse<{\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n }>(readFileSync(packageJsonPath, 'utf-8'));\n\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n ...packageJson.peerDependencies,\n };\n\n return Object.keys(allDeps).filter((pkgName) => {\n if (!pkgName.startsWith('@happyvertical/smrt-')) {\n return false;\n }\n\n const hasManifest = hasManifestExport(pkgName, baseDir);\n if (verbose && hasManifest) {\n console.log(`[discovery] ✅ Found declared SMRT package: ${pkgName}`);\n }\n\n return hasManifest;\n });\n } catch (error) {\n if (verbose) {\n console.warn(\n '[discovery] Failed to read declared dependencies:',\n (error as Error).message,\n );\n }\n return [];\n }\n}\n\n/**\n * Scan node_modules recursively for packages\n */\nfunction* scanNodeModules(baseDir: string): Generator<string> {\n const nodeModulesPath = join(baseDir, 'node_modules');\n\n if (!existsSync(nodeModulesPath)) {\n return;\n }\n\n try {\n const entries = readdirSync(nodeModulesPath);\n\n for (const entry of entries) {\n if (entry === '.bin' || entry === '.pnpm' || entry === '.cache') {\n continue;\n }\n\n const entryPath = join(nodeModulesPath, entry);\n\n try {\n const stats = statSync(entryPath);\n\n if (stats.isDirectory() || stats.isSymbolicLink()) {\n // Scoped packages (e.g., @happyvertical/smrt-core)\n if (entry.startsWith('@')) {\n const scopeEntries = readdirSync(entryPath);\n for (const scopedPkg of scopeEntries) {\n yield `${entry}/${scopedPkg}`;\n }\n } else {\n // Regular packages\n yield entry;\n }\n }\n } catch {}\n }\n } catch (error) {\n // node_modules doesn't exist or can't be read\n return;\n }\n}\n\n/**\n * Perform fresh discovery of SMRT packages\n */\nfunction performDiscovery(baseDir: string, verbose: boolean): string[] {\n if (verbose) {\n console.log('[discovery] Scanning node_modules for SMRT packages...');\n }\n\n try {\n const smrtPackages = new Set<string>();\n\n // Scan node_modules for all packages\n for (const pkgName of scanNodeModules(baseDir)) {\n if (hasManifestExport(pkgName, baseDir)) {\n smrtPackages.add(pkgName);\n if (verbose) {\n console.log(`[discovery] ✅ Found SMRT package: ${pkgName}`);\n }\n }\n }\n\n for (const pkgName of getDeclaredSmrtPackages(baseDir, verbose)) {\n smrtPackages.add(pkgName);\n }\n\n if (verbose) {\n console.log(\n `[discovery] Discovered ${smrtPackages.size} SMRT package(s)`,\n );\n }\n\n return Array.from(smrtPackages);\n } catch (error) {\n console.error(\n '[discovery] Failed to discover packages:',\n (error as Error).message,\n );\n return [];\n }\n}\n\nexport interface DiscoveryOptions {\n /** Override project root for discovery/cache */\n baseDir?: string;\n /** Force fresh discovery, ignoring cache */\n noCache?: boolean;\n /** Show verbose output */\n verbose?: boolean;\n /** Record timing data */\n timing?: boolean;\n}\n\n/**\n * Main discovery function\n *\n * Cache ENABLED by default (5-50x faster startup)\n * - Automatically invalidates when lockfile changes (dependencies updated)\n * - Automatically invalidates when any manifest.json changes (packages rebuilt)\n *\n * Disable with SMRT_DISABLE_DISCOVERY_CACHE=true for debugging\n *\n * Intentional split (#1579): this is the **build-time** discovery path —\n * synchronous, scans `node_modules` for `manifest.json` files with\n * `moduleType: \"smrt\"`, and caches by lockfile/manifest hash for fast manifest\n * generation. It is deliberately distinct from the consumer-plugin's\n * `discoverSmrtPackages(projectRoot)` (`src/consumer-plugin/index.ts`), which is\n * **async**, reads a downstream app's `package.json` dependency names with a\n * lightweight `@have/`/`smrt` heuristic, and runs inside the Vite consumer\n * plugin. Different inputs, contexts, and lifecycles — not duplicated logic to\n * consolidate.\n */\nexport function discoverSmrtPackages(options: DiscoveryOptions = {}): string[] {\n const startTime = options.timing ? performance.now() : 0;\n lastTimingData = {};\n const baseDir = options.baseDir || process.cwd();\n\n const cacheDisabled =\n options.noCache || process.env.SMRT_DISABLE_DISCOVERY_CACHE === 'true';\n\n const verbose: boolean =\n options.verbose === true ||\n process.env.SMRT_VERBOSE === 'true' ||\n !!process.env.DEBUG?.includes('smrt');\n\n if (cacheDisabled) {\n if (verbose) {\n console.log('[discovery] Cache disabled, performing fresh discovery...');\n }\n\n const packages = performDiscovery(baseDir, verbose);\n\n if (options.timing) {\n lastTimingData.discovery = performance.now() - startTime;\n lastTimingData.total = lastTimingData.discovery;\n }\n\n return packages;\n }\n\n // Try cache first\n const cacheCheckStart = options.timing ? performance.now() : 0;\n const cached = getCachedDiscovery(baseDir, verbose);\n\n if (options.timing) {\n lastTimingData.cacheCheck = performance.now() - cacheCheckStart;\n }\n\n if (cached) {\n if (verbose) {\n console.log(\n `[discovery] ✅ Using cached SMRT packages (${cached.packages.length} package(s))`,\n );\n }\n\n if (options.timing) {\n lastTimingData.total = performance.now() - startTime;\n }\n\n return cached.packages;\n }\n\n // No valid cache - perform fresh discovery\n if (verbose) {\n console.log('[discovery] Cache miss, performing discovery...');\n }\n\n const discoveryStart = options.timing ? performance.now() : 0;\n const packages = performDiscovery(baseDir, verbose);\n\n if (options.timing) {\n lastTimingData.discovery = performance.now() - discoveryStart;\n }\n\n // Save to cache\n saveCachedDiscovery(baseDir, packages, verbose);\n\n if (options.timing) {\n lastTimingData.total = performance.now() - startTime;\n }\n\n return packages;\n}\n\n// Run if called directly\nif (import.meta.url === `file://${process.argv[1]}`) {\n const noCache = process.argv.includes('--no-cache');\n const verbose =\n process.argv.includes('--verbose') || process.argv.includes('-v');\n const timing = process.argv.includes('--timing');\n\n const packages = discoverSmrtPackages({ noCache, verbose, timing });\n\n console.log('\\nDiscovered SMRT packages:');\n console.log(JSON.stringify(packages, null, 2));\n\n if (timing) {\n const timingData = getDiscoveryTiming();\n console.log('\\nTiming:');\n if (timingData.cacheCheck !== undefined) {\n console.log(` Cache check: ${timingData.cacheCheck.toFixed(2)}ms`);\n }\n if (timingData.discovery !== undefined) {\n console.log(` Discovery: ${timingData.discovery.toFixed(2)}ms`);\n }\n if (timingData.total !== undefined) {\n console.log(` Total: ${timingData.total.toFixed(2)}ms`);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -23,12 +23,16 @@ export interface ManifestBuilderOptions {
|
|
|
23
23
|
includeExternalBaseClasses?: boolean;
|
|
24
24
|
includePrivateMethods?: boolean;
|
|
25
25
|
includeStaticMethods?: boolean;
|
|
26
|
+
/** Explicit output ownership; omitted retains legacy filename inference. */
|
|
27
|
+
outputMode?: 'dev' | 'build';
|
|
26
28
|
outputDir?: string;
|
|
27
29
|
outputName?: string;
|
|
28
30
|
generateTypeStub?: boolean;
|
|
29
31
|
stubName?: string;
|
|
30
32
|
injectPackageInfo?: boolean;
|
|
31
33
|
moduleType?: string;
|
|
34
|
+
/** Test artifacts are local inputs, never dependency discovery providers. */
|
|
35
|
+
artifactPurpose?: 'runtime' | 'test';
|
|
32
36
|
/**
|
|
33
37
|
* @deprecated Tree shaking is no longer needed. The AST scanner no longer includes external objects in the manifest by default.
|
|
34
38
|
*/
|
|
@@ -45,6 +49,9 @@ export interface ManifestBuilderOptions {
|
|
|
45
49
|
* into a single, testable, and maintainable service.
|
|
46
50
|
*/
|
|
47
51
|
export declare class ManifestBuilder {
|
|
52
|
+
private readonly projectRoot?;
|
|
53
|
+
constructor(projectRoot?: string | undefined);
|
|
54
|
+
private root;
|
|
48
55
|
/**
|
|
49
56
|
* Generate manifest with specified options
|
|
50
57
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/manifest/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/manifest/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AA0B/D;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IAErC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAG9B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAG/B,4EAA4E;IAC5E,UAAU,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,eAAe,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAGrC;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC;AAuCD;;;;;GAKG;AACH,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAAZ,WAAW,CAAC,EAAE,MAAM,YAAA;IAEjD,OAAO,CAAC,IAAI;IAGZ;;OAEG;IACG,QAAQ,CACZ,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,mBAAmB,CAAC;IAkC/B,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,iBAAiB;IAmBzB;;OAEG;YACW,aAAa;IAc3B;;OAEG;YACW,gBAAgB;IAoD9B;;;;;OAKG;YACW,oBAAoB;IAuElC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAwCtB;;OAEG;YACW,WAAW;IA4CzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuCxB;;OAEG;YACW,yBAAyB;IAgFvC;;OAEG;YACW,uBAAuB;IAwCrC;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;OAEG;IACH,OAAO,CAAC,mBAAmB;CA4B5B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "../scanner/types.js";
|
|
2
|
+
import { publishAtomicArtifact } from "../consumer-plugin/artifact-publication.js";
|
|
2
3
|
import { VERBOSE_ENABLED } from "../registry/shared-state.js";
|
|
3
4
|
import { ManifestGenerator } from "../scanner/manifest-generator.js";
|
|
4
5
|
import { ManifestManager } from "./manager.js";
|
|
@@ -29,6 +30,13 @@ async function importScanner() {
|
|
|
29
30
|
* into a single, testable, and maintainable service.
|
|
30
31
|
*/
|
|
31
32
|
var ManifestBuilder = class {
|
|
33
|
+
projectRoot;
|
|
34
|
+
constructor(projectRoot) {
|
|
35
|
+
this.projectRoot = projectRoot;
|
|
36
|
+
}
|
|
37
|
+
root() {
|
|
38
|
+
return this.projectRoot ?? process.cwd();
|
|
39
|
+
}
|
|
32
40
|
/**
|
|
33
41
|
* Generate manifest with specified options
|
|
34
42
|
*/
|
|
@@ -47,7 +55,7 @@ var ManifestBuilder = class {
|
|
|
47
55
|
return manifest;
|
|
48
56
|
}
|
|
49
57
|
normalizeFilePaths(manifest) {
|
|
50
|
-
const workspaceRoot = this.findWorkspaceRoot(
|
|
58
|
+
const workspaceRoot = this.findWorkspaceRoot(this.root());
|
|
51
59
|
for (const obj of Object.values(manifest.objects || {})) if (obj.filePath && isAbsolute(obj.filePath)) obj.filePath = relative(workspaceRoot, obj.filePath).replace(/\\/g, "/");
|
|
52
60
|
}
|
|
53
61
|
findWorkspaceRoot(startDir) {
|
|
@@ -67,7 +75,7 @@ var ManifestBuilder = class {
|
|
|
67
75
|
const exclude = options.exclude || ["src/**/*.d.ts", "node_modules/**"];
|
|
68
76
|
const { discoverSourceFiles } = await importScanner();
|
|
69
77
|
return discoverSourceFiles({
|
|
70
|
-
cwd:
|
|
78
|
+
cwd: this.root(),
|
|
71
79
|
include,
|
|
72
80
|
exclude,
|
|
73
81
|
followSymbolicLinks: options.followSymbolicLinks ?? false
|
|
@@ -92,7 +100,7 @@ var ManifestBuilder = class {
|
|
|
92
100
|
let smrtDependencies = [];
|
|
93
101
|
if (options.discoverExternalPackages) {
|
|
94
102
|
logger.debug("[smrt] Discovering external SMRT packages...");
|
|
95
|
-
smrtDependencies = discoverSmrtPackages();
|
|
103
|
+
smrtDependencies = discoverSmrtPackages({ baseDir: this.root() });
|
|
96
104
|
logger.debug(`[smrt] Found ${smrtDependencies.length} SMRT package(s): ${smrtDependencies.join(", ")}`);
|
|
97
105
|
if (options.includeExternalBaseClasses) {
|
|
98
106
|
const externalClasses = await this.loadExternalBaseClasses(smrtDependencies);
|
|
@@ -120,7 +128,7 @@ var ManifestBuilder = class {
|
|
|
120
128
|
async scanAndBuildManifest(config, options) {
|
|
121
129
|
const { OxcScanner, ManifestAdapter } = await importScanner();
|
|
122
130
|
const { results, resolved } = await new OxcScanner({
|
|
123
|
-
cwd:
|
|
131
|
+
cwd: this.root(),
|
|
124
132
|
include: options.include || ["src/**/*.ts"],
|
|
125
133
|
exclude: options.exclude || ["src/**/*.d.ts", "node_modules/**"],
|
|
126
134
|
baseClasses: config.baseClasses,
|
|
@@ -140,7 +148,7 @@ var ManifestBuilder = class {
|
|
|
140
148
|
let packageVersion;
|
|
141
149
|
let packageJson;
|
|
142
150
|
try {
|
|
143
|
-
const pkgContent = readFileSync(resolve(
|
|
151
|
+
const pkgContent = readFileSync(resolve(this.root(), "package.json"), "utf-8");
|
|
144
152
|
packageJson = JSON.parse(pkgContent);
|
|
145
153
|
packageName = packageJson?.name || void 0;
|
|
146
154
|
packageVersion = packageJson?.version || void 0;
|
|
@@ -166,6 +174,8 @@ var ManifestBuilder = class {
|
|
|
166
174
|
*/
|
|
167
175
|
enrichManifest(manifest, options, scannerConfig) {
|
|
168
176
|
manifest.moduleType = options.moduleType || "smrt";
|
|
177
|
+
const purpose = options.artifactPurpose ?? (options.outputName?.includes("test") || options.stubName?.includes("test") ? "test" : void 0);
|
|
178
|
+
if (purpose) manifest.artifactPurpose = purpose;
|
|
169
179
|
if (scannerConfig.smrtDependencies && scannerConfig.smrtDependencies.length > 0) manifest.smrtDependencies = scannerConfig.smrtDependencies;
|
|
170
180
|
if (options.injectPackageInfo) {
|
|
171
181
|
const packageInfo = this.readPackageJson();
|
|
@@ -180,15 +190,19 @@ var ManifestBuilder = class {
|
|
|
180
190
|
* Write manifest JSON and optional TypeScript stub
|
|
181
191
|
*/
|
|
182
192
|
async writeOutput(manifest, options) {
|
|
183
|
-
const manager = new ManifestManager(
|
|
184
|
-
const
|
|
193
|
+
const manager = new ManifestManager(this.root());
|
|
194
|
+
const isTest = options.outputName?.includes("test") || options.stubName?.includes("test");
|
|
195
|
+
const mode = options.outputMode ?? (isTest ? "dev" : "build");
|
|
185
196
|
manager.write(manifest, mode);
|
|
186
|
-
const outputDir = options.outputDir || "src/manifest";
|
|
197
|
+
const outputDir = resolve(this.root(), options.outputDir || "src/manifest");
|
|
187
198
|
const outputName = options.outputName || "manifest.json";
|
|
188
199
|
mkdirSync(outputDir, { recursive: true });
|
|
189
200
|
const manifestPath = resolve(outputDir, outputName);
|
|
190
201
|
const unifiedPath = manager.getOutputPath(mode);
|
|
191
|
-
if (resolve(manifestPath) !== resolve(unifiedPath))
|
|
202
|
+
if (resolve(manifestPath) !== resolve(unifiedPath)) publishAtomicArtifact({
|
|
203
|
+
path: manifestPath,
|
|
204
|
+
content: JSON.stringify(manifest, null, 2)
|
|
205
|
+
});
|
|
192
206
|
if (options.generateTypeStub) {
|
|
193
207
|
const stubName = options.stubName || "manifest.ts";
|
|
194
208
|
const tsContent = this.generateTypeStub(manifest, stubName);
|
|
@@ -229,13 +243,13 @@ export default ${exportName};
|
|
|
229
243
|
*/
|
|
230
244
|
async loadViteConfigBaseClasses() {
|
|
231
245
|
try {
|
|
232
|
-
const viteConfigPath = resolve(
|
|
246
|
+
const viteConfigPath = resolve(this.root(), "vite.config.ts");
|
|
233
247
|
if (!existsSync(viteConfigPath)) {
|
|
234
248
|
logger.debug("[smrt] vite.config.ts not found");
|
|
235
249
|
return null;
|
|
236
250
|
}
|
|
237
251
|
logger.debug("[smrt] Found vite.config.ts, attempting to load...");
|
|
238
|
-
const { loadConfigFromFile } = await importProjectVite(
|
|
252
|
+
const { loadConfigFromFile } = await importProjectVite(this.root(), "Loading vite.config.ts");
|
|
239
253
|
const loaded = await loadConfigFromFile({
|
|
240
254
|
command: "build",
|
|
241
255
|
mode: "test"
|
|
@@ -280,7 +294,7 @@ export default ${exportName};
|
|
|
280
294
|
async loadExternalBaseClasses(packages) {
|
|
281
295
|
const externalBaseClasses = [];
|
|
282
296
|
for (const pkgName of packages) {
|
|
283
|
-
const manifestPath = resolveManifestPath(pkgName,
|
|
297
|
+
const manifestPath = resolveManifestPath(pkgName, this.root());
|
|
284
298
|
if (!manifestPath) {
|
|
285
299
|
logger.debug(`[smrt] ${pkgName}: no SMRT manifest resolved`);
|
|
286
300
|
continue;
|
|
@@ -306,7 +320,7 @@ export default ${exportName};
|
|
|
306
320
|
*/
|
|
307
321
|
readPackageJson() {
|
|
308
322
|
try {
|
|
309
|
-
const packageJsonPath = resolve(
|
|
323
|
+
const packageJsonPath = resolve(this.root(), "package.json");
|
|
310
324
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
311
325
|
return {
|
|
312
326
|
name: packageJson.name,
|
|
@@ -324,9 +338,10 @@ export default ${exportName};
|
|
|
324
338
|
version: "1.0.0",
|
|
325
339
|
timestamp: 0,
|
|
326
340
|
objects: {},
|
|
327
|
-
moduleType: options.moduleType || "smrt"
|
|
341
|
+
moduleType: options.moduleType || "smrt",
|
|
342
|
+
...options.artifactPurpose ? { artifactPurpose: options.artifactPurpose } : {}
|
|
328
343
|
};
|
|
329
|
-
if (options.discoverExternalPackages) manifest.smrtDependencies = discoverSmrtPackages();
|
|
344
|
+
if (options.discoverExternalPackages) manifest.smrtDependencies = discoverSmrtPackages({ baseDir: this.root() });
|
|
330
345
|
if (options.injectPackageInfo) {
|
|
331
346
|
const packageInfo = this.readPackageJson();
|
|
332
347
|
if (packageInfo.name) manifest.packageName = packageInfo.name;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","names":[],"sources":["../../src/manifest/generator.ts"],"sourcesContent":["/**\n * Manifest generation orchestrator\n * Consolidates manifest generation logic for both build and test manifests\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { createLogger } from '@happyvertical/logger';\nimport { VERBOSE_ENABLED } from '../registry/shared-state.js';\nimport { ManifestGenerator } from '../scanner/manifest-generator.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { MANIFEST_TIMESTAMP } from '../scanner/types.js';\nimport { importProjectVite } from '../utils/import-project-vite.js';\nimport { importWorkspaceModule } from '../utils/import-workspace-module.js';\nimport type { ScannerModule } from '../utils/scanner-module.js';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\nimport { ManifestManager } from './manager.js';\n\n// Build-time manifest-generation diagnostics route through the shared logger\n// (S14 / dim-9, #1579) so consumers can control verbosity instead of receiving\n// unconditional stdout writes. Level follows the repo-wide SMRT_VERBOSE/DEBUG\n// convention (#2896) so per-run detail is opt-in rather than always-on info.\nconst logger = createLogger({ level: VERBOSE_ENABLED ? 'debug' : 'info' });\n\nasync function importScanner() {\n return importWorkspaceModule<ScannerModule>({\n packageName: '@happyvertical/smrt-scanner',\n distEntry: 'packages/scanner/dist/index.js',\n sourceEntry: 'packages/scanner/src/index.ts',\n purpose: 'manifest generation',\n });\n}\n\n/**\n * Options for ManifestBuilder.generate()\n *\n * Controls file discovery, scanner configuration, output generation, and metadata injection\n */\nexport interface ManifestBuilderOptions {\n // File Discovery\n include?: string[];\n exclude?: string[];\n followImports?: boolean;\n /**\n * Follow symbolic links while discovering sources. Defaults to `false` —\n * see `OxcScannerOptions.followSymbolicLinks` (#2275).\n */\n followSymbolicLinks?: boolean;\n\n // Scanner Configuration\n baseClasses?: string[];\n loadViteConfig?: boolean;\n discoverExternalPackages?: boolean;\n includeExternalBaseClasses?: boolean;\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n\n // Output Configuration\n outputDir?: string;\n outputName?: string;\n generateTypeStub?: boolean;\n stubName?: string;\n\n // Metadata\n injectPackageInfo?: boolean;\n moduleType?: string;\n\n // Tree Shaking (External Object Filtering)\n /**\n * @deprecated Tree shaking is no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n treeShake?: boolean;\n\n /**\n * @deprecated Whitelists are no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n externalObjectsWhitelist?: string[];\n}\n\ninterface ScannerConfig {\n baseClasses: string[];\n includePrivateMethods: boolean;\n includeStaticMethods: boolean;\n followImports: boolean;\n smrtDependencies?: string[];\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/**\n * Subset of the smrt-auto-service Vite plugin options we probe when reading\n * baseClasses out of a project's vite.config.ts.\n */\ninterface VitePluginOptions {\n baseClasses?: string[];\n followImports?: boolean;\n}\n\n/**\n * Structural shape of a loaded Vite plugin entry as probed by\n * loadViteConfigBaseClasses(). The plugin may expose its options directly, on\n * `_options`, or via an `api` object/function — all optional and discovered at\n * runtime, so each is modeled as optional here.\n */\ninterface VitePluginProbe {\n name?: string;\n options?: VitePluginOptions;\n _options?: VitePluginOptions;\n api?:\n | { options?: VitePluginOptions }\n | (() => { options?: VitePluginOptions });\n}\n\n/**\n * Orchestrates manifest generation with configurable options\n *\n * Consolidates logic from generate-manifest.js and generate-test-manifest.js\n * into a single, testable, and maintainable service.\n */\nexport class ManifestBuilder {\n /**\n * Generate manifest with specified options\n */\n async generate(\n options: ManifestBuilderOptions = {},\n ): Promise<SmartObjectManifest> {\n // 1. Discover files to scan\n const files = await this.discoverFiles(options);\n\n if (files.length === 0) {\n logger.debug('[smrt] No source files found');\n return this.createEmptyManifest(options);\n }\n\n logger.debug(`[smrt] Scanning ${files.length} source file(s)...`);\n\n // 2. Configure scanner (baseClasses, external packages, vite config)\n const scannerConfig = await this.configureScanner(options);\n\n // 3. Scan files with OXC scanner\n const scannedManifest = await this.scanAndBuildManifest(\n scannerConfig,\n options,\n );\n\n // 4. Enrich manifest with metadata (smrtDependencies, packageInfo)\n const manifest = this.enrichManifest(\n scannedManifest,\n options,\n scannerConfig,\n );\n\n // 5. Write output files\n this.normalizeFilePaths(manifest);\n await this.writeOutput(manifest, options);\n\n return manifest;\n }\n\n private normalizeFilePaths(manifest: SmartObjectManifest): void {\n const workspaceRoot = this.findWorkspaceRoot(process.cwd());\n\n for (const obj of Object.values(manifest.objects || {})) {\n if (obj.filePath && isAbsolute(obj.filePath)) {\n obj.filePath = relative(workspaceRoot, obj.filePath).replace(\n /\\\\/g,\n '/',\n );\n }\n }\n }\n\n private findWorkspaceRoot(startDir: string): string {\n let current = resolve(startDir);\n\n while (true) {\n if (\n existsSync(resolve(current, 'pnpm-workspace.yaml')) ||\n existsSync(resolve(current, '.git'))\n ) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return resolve(startDir);\n }\n current = parent;\n }\n }\n\n /**\n * Discover source files based on include/exclude patterns\n */\n private async discoverFiles(\n options: ManifestBuilderOptions,\n ): Promise<string[]> {\n const include = options.include || ['src/**/*.ts'];\n const exclude = options.exclude || ['src/**/*.d.ts', 'node_modules/**'];\n const { discoverSourceFiles } = await importScanner();\n return discoverSourceFiles({\n cwd: process.cwd(),\n include,\n exclude,\n followSymbolicLinks: options.followSymbolicLinks ?? false,\n });\n }\n\n /**\n * Configure AST scanner with baseClasses, external packages, and vite config\n */\n private async configureScanner(\n options: ManifestBuilderOptions,\n ): Promise<ScannerConfig> {\n let baseClasses = options.baseClasses || [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n ];\n\n // Load from vite.config.ts if requested\n if (options.loadViteConfig) {\n const viteBaseClasses = await this.loadViteConfigBaseClasses();\n if (viteBaseClasses && viteBaseClasses.length > 0) {\n baseClasses = viteBaseClasses;\n logger.debug(\n `[smrt] Using baseClasses from vite.config.ts: ${baseClasses.join(', ')}`,\n );\n }\n }\n\n // Discover external SMRT packages\n let smrtDependencies: string[] = [];\n if (options.discoverExternalPackages) {\n logger.debug('[smrt] Discovering external SMRT packages...');\n smrtDependencies = discoverSmrtPackages();\n logger.debug(\n `[smrt] Found ${smrtDependencies.length} SMRT package(s): ${smrtDependencies.join(', ')}`,\n );\n\n // Optionally add external base classes\n if (options.includeExternalBaseClasses) {\n const externalClasses =\n await this.loadExternalBaseClasses(smrtDependencies);\n if (externalClasses.length > 0) {\n baseClasses = [...baseClasses, ...externalClasses];\n logger.debug(\n `[smrt] Added ${externalClasses.length} external base class(es) to scanner`,\n );\n logger.debug(`[smrt] Total baseClasses: ${baseClasses.length}`);\n }\n }\n }\n\n return {\n baseClasses,\n includePrivateMethods: options.includePrivateMethods ?? false,\n includeStaticMethods: options.includeStaticMethods ?? true,\n followImports: options.followImports ?? false,\n smrtDependencies,\n };\n }\n\n /**\n * Scan files using OxcScanner and build manifest via ManifestAdapter.\n *\n * This replaces the old ASTScanner-based scanFiles() method.\n * Uses the same OXC pipeline as the Vite plugin's scanWithOxc().\n */\n private async scanAndBuildManifest(\n config: ScannerConfig,\n options: ManifestBuilderOptions,\n ): Promise<SmartObjectManifest> {\n const { OxcScanner, ManifestAdapter } = await importScanner();\n\n const scanner = new OxcScanner({\n cwd: process.cwd(),\n include: options.include || ['src/**/*.ts'],\n exclude: options.exclude || ['src/**/*.d.ts', 'node_modules/**'],\n baseClasses: config.baseClasses,\n includePrivateMethods: config.includePrivateMethods,\n includeStaticMethods: config.includeStaticMethods,\n followImports: config.followImports,\n followSymbolicLinks: options.followSymbolicLinks,\n });\n\n const { results, resolved } = await scanner.scanAndResolve();\n const scanErrors = results.errors.filter(\n (diagnostic) => diagnostic.severity === 'error',\n );\n if (scanErrors.length > 0) {\n const detail = scanErrors\n .map((diagnostic) => {\n const where = diagnostic.line\n ? `${diagnostic.filePath}:${diagnostic.line}`\n : diagnostic.filePath;\n return `${where} — ${diagnostic.message}`;\n })\n .join('; ');\n throw new Error(\n `[smrt] Build aborted due to ${scanErrors.length} scan error(s): ${detail}`,\n );\n }\n\n // Read package.json for metadata\n let packageName: string | undefined;\n let packageVersion: string | undefined;\n let packageJson: { name?: string; version?: string } | undefined;\n try {\n const pkgPath = resolve(process.cwd(), 'package.json');\n const pkgContent = readFileSync(pkgPath, 'utf-8');\n packageJson = JSON.parse(pkgContent);\n packageName = packageJson?.name || undefined;\n packageVersion = packageJson?.version || undefined;\n } catch {\n // package.json not found - continue without packageName\n }\n\n // Convert to manifest format\n const adapter = new ManifestAdapter();\n const manifest = adapter.toManifest(resolved, {\n packageName,\n packageVersion,\n typeAliases: results.typeAliases,\n });\n\n // Set smrtDependencies BEFORE mergeInheritedFields so external packages can be loaded\n if (config.smrtDependencies && config.smrtDependencies.length > 0) {\n manifest.smrtDependencies = config.smrtDependencies;\n }\n\n // Run THE manifest generation pass sequence (shared with\n // ManifestGenerator.generateManifest() and the Vite plugin, #2360 — this\n // path used to skip the report and conflict-key passes).\n const manifestGen = new ManifestGenerator();\n manifestGen.applyGenerationPasses(manifest, { packageName, packageJson });\n\n return manifest;\n }\n\n /**\n * Enrich manifest with metadata (moduleType, smrtDependencies, packageInfo).\n *\n * Note: As of Issue #1013, this method no longer merges external package\n * objects into the manifest. Dependencies are referenced via\n * `smrtDependencies` and resolved at runtime by manifest-loader.ts.\n */\n private enrichManifest(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n scannerConfig: ScannerConfig,\n ): SmartObjectManifest {\n // Add module type\n manifest.moduleType = options.moduleType || 'smrt';\n\n // Record dependency references (not their objects) so runtime\n // manifest-loader.ts can discover and load them on demand.\n if (\n scannerConfig.smrtDependencies &&\n scannerConfig.smrtDependencies.length > 0\n ) {\n manifest.smrtDependencies = scannerConfig.smrtDependencies;\n }\n\n // Inject package info\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n logger.debug(`[smrt] Injected package name: ${packageInfo.name}`);\n } else {\n logger.warn(\n '[smrt] Warning: Could not read package.json, packageName will be undefined',\n );\n }\n }\n\n return manifest;\n }\n\n /**\n * Write manifest JSON and optional TypeScript stub\n */\n private async writeOutput(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n ): Promise<void> {\n const manager = new ManifestManager(process.cwd());\n const isTest =\n options.outputName?.includes('test') ||\n options.stubName?.includes('test');\n const mode = isTest ? 'dev' : 'build';\n\n // 1. Always write to the unified location via ManifestManager\n manager.write(manifest, mode);\n\n // 2. Legacy/Explicit Output (if requested or for stubs)\n const outputDir = options.outputDir || 'src/manifest';\n const outputName = options.outputName || 'manifest.json';\n\n // Ensure output directory exists for legacy/stubs\n mkdirSync(outputDir, { recursive: true });\n\n // Write JSON manifest to legacy location if different from unified path\n const manifestPath = resolve(outputDir, outputName);\n const unifiedPath = manager.getOutputPath(mode);\n\n if (resolve(manifestPath) !== resolve(unifiedPath)) {\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n }\n\n // Write TypeScript stub if requested\n if (options.generateTypeStub) {\n const stubName = options.stubName || 'manifest.ts';\n const tsContent = this.generateTypeStub(manifest, stubName);\n const stubPath = resolve(outputDir, stubName);\n writeFileSync(stubPath, tsContent);\n }\n\n const objectCount = Object.keys(manifest.objects).length;\n logger.info(`[smrt] ✅ Generated manifest with ${objectCount} object(s)`);\n logger.debug(`[smrt] Unified: ${unifiedPath}`);\n }\n\n /**\n * Generate TypeScript stub file with manifest constant\n */\n private generateTypeStub(\n manifest: SmartObjectManifest,\n filename: string,\n ): string {\n const isTestManifest = filename.includes('test');\n\n const comment = isTestManifest\n ? `/**\n * Test manifest stub\n * This file is replaced by generate-test-manifest.js during pretest\n * If tests run without pretest, this empty manifest is used\n *\n * DO NOT EDIT MANUALLY - This file is automatically generated\n */`\n : `/**\n * Auto-generated static manifest\n * Generated at build time from SMRT object scanning\n * DO NOT EDIT - This file is automatically generated\n */`;\n\n const exportName = isTestManifest ? 'testManifest' : 'staticManifest';\n\n // Use relative import for core package (to avoid self-reference during build),\n // package import for all other packages\n const isCorePackage = manifest.packageName === '@happyvertical/smrt-core';\n const importPath = isCorePackage\n ? '../scanner/types.js'\n : '@happyvertical/smrt-core/scanner/types';\n\n return `${comment}\n\nimport type { SmartObjectManifest } from '${importPath}';\n\nexport const ${exportName}: SmartObjectManifest = ${JSON.stringify(manifest, null, 2)} as const;\n\nexport default ${exportName};\n`;\n }\n\n /**\n * Load baseClasses from vite.config.ts\n */\n private async loadViteConfigBaseClasses(): Promise<string[] | null> {\n try {\n const viteConfigPath = resolve(process.cwd(), 'vite.config.ts');\n if (!existsSync(viteConfigPath)) {\n logger.debug('[smrt] vite.config.ts not found');\n return null;\n }\n\n logger.debug('[smrt] Found vite.config.ts, attempting to load...');\n\n // Use vite to load config which handles TypeScript\n const { loadConfigFromFile } = await importProjectVite(\n process.cwd(),\n 'Loading vite.config.ts',\n );\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'test' },\n viteConfigPath,\n );\n\n if (!loaded?.config?.plugins) {\n logger.debug('[smrt] No plugins found in vite config');\n return null;\n }\n\n // Vite's loaded plugins are a recursive PluginOption[] (plugins, arrays,\n // falsy values, promises). We only probe a few optional fields, so model\n // them structurally and narrow defensively instead of using `any`.\n const plugins = loaded.config.plugins as readonly VitePluginProbe[];\n\n logger.debug('[smrt] Vite config loaded', {\n plugins: plugins.map((p) => p?.name),\n });\n\n // Find smrtPlugin in plugins array\n const smrtPlugin = plugins.find((p) => p?.name === 'smrt-auto-service');\n\n if (!smrtPlugin) {\n logger.debug(\n '[smrt] smrt-auto-service plugin not found in plugins array',\n );\n return null;\n }\n\n logger.debug('[smrt] Found smrt-auto-service plugin');\n\n // Try different ways to access options\n const api = smrtPlugin.api;\n let opts: VitePluginOptions | undefined =\n (typeof api === 'object' && api !== null ? api.options : undefined) ||\n smrtPlugin.options ||\n smrtPlugin._options;\n\n if (!opts && typeof api === 'function') {\n try {\n opts = api().options;\n } catch (e) {\n logger.debug('[smrt] Could not call plugin.api()');\n }\n }\n\n if (opts?.baseClasses) {\n logger.debug('[smrt] Plugin options', {\n baseClasses: opts.baseClasses,\n followImports: opts.followImports,\n });\n return opts.baseClasses;\n }\n\n logger.debug('[smrt] Could not access plugin options');\n return null;\n } catch (error) {\n logger.warn('[smrt] Error loading vite.config.ts', {\n error: error instanceof Error ? error.message : String(error),\n });\n logger.debug('[smrt] Using default baseClasses');\n return null;\n }\n }\n\n /**\n * Load base classes from external SMRT package manifests\n */\n private async loadExternalBaseClasses(packages: string[]): Promise<string[]> {\n const externalBaseClasses: string[] = [];\n\n for (const pkgName of packages) {\n // Resolve the manifest the same way discovery does (honors `.smrt/` and\n // `src/manifest/`, not just `dist/`) so source-only workspace packages\n // still contribute base classes (#1378).\n const manifestPath = resolveManifestPath(pkgName, process.cwd());\n if (!manifestPath) {\n logger.debug(`[smrt] ${pkgName}: no SMRT manifest resolved`);\n continue;\n }\n\n try {\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest: SmartObjectManifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n const foundClassNames: string[] = [];\n for (const objDef of Object.values(manifest.objects)) {\n if (objDef.className) {\n const className = objDef.className;\n foundClassNames.push(className);\n externalBaseClasses.push(className);\n }\n }\n\n logger.debug(\n `[smrt] ${pkgName}: found ${foundClassNames.length} class(es) - ${foundClassNames.join(', ')}`,\n );\n } catch (error) {\n logger.debug(\n `[smrt] ${pkgName}: manifest not found or invalid - ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return externalBaseClasses;\n }\n\n /**\n * Read package.json from current working directory\n */\n private readPackageJson(): PackageInfo {\n try {\n const packageJsonPath = resolve(process.cwd(), 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return {\n name: packageJson.name,\n version: packageJson.version,\n };\n } catch (error) {\n return {};\n }\n }\n\n /**\n * Create empty manifest when no files found\n */\n private createEmptyManifest(\n options: ManifestBuilderOptions,\n ): SmartObjectManifest {\n const manifest: SmartObjectManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n moduleType: options.moduleType || 'smrt',\n };\n\n if (options.discoverExternalPackages) {\n manifest.smrtDependencies = discoverSmrtPackages();\n }\n\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n }\n }\n\n return manifest;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generator.js","names":[],"sources":["../../src/manifest/generator.ts"],"sourcesContent":["/**\n * Manifest generation orchestrator\n * Consolidates manifest generation logic for both build and test manifests\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { createLogger } from '@happyvertical/logger';\nimport { publishAtomicArtifact } from '../consumer-plugin/artifact-publication.js';\nimport { VERBOSE_ENABLED } from '../registry/shared-state.js';\nimport { ManifestGenerator } from '../scanner/manifest-generator.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { MANIFEST_TIMESTAMP } from '../scanner/types.js';\nimport { importProjectVite } from '../utils/import-project-vite.js';\nimport { importWorkspaceModule } from '../utils/import-workspace-module.js';\nimport type { ScannerModule } from '../utils/scanner-module.js';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\nimport { ManifestManager } from './manager.js';\n\n// Build-time manifest-generation diagnostics route through the shared logger\n// (S14 / dim-9, #1579) so consumers can control verbosity instead of receiving\n// unconditional stdout writes. Level follows the repo-wide SMRT_VERBOSE/DEBUG\n// convention (#2896) so per-run detail is opt-in rather than always-on info.\nconst logger = createLogger({ level: VERBOSE_ENABLED ? 'debug' : 'info' });\n\nasync function importScanner() {\n return importWorkspaceModule<ScannerModule>({\n packageName: '@happyvertical/smrt-scanner',\n distEntry: 'packages/scanner/dist/index.js',\n sourceEntry: 'packages/scanner/src/index.ts',\n purpose: 'manifest generation',\n });\n}\n\n/**\n * Options for ManifestBuilder.generate()\n *\n * Controls file discovery, scanner configuration, output generation, and metadata injection\n */\nexport interface ManifestBuilderOptions {\n // File Discovery\n include?: string[];\n exclude?: string[];\n followImports?: boolean;\n /**\n * Follow symbolic links while discovering sources. Defaults to `false` —\n * see `OxcScannerOptions.followSymbolicLinks` (#2275).\n */\n followSymbolicLinks?: boolean;\n\n // Scanner Configuration\n baseClasses?: string[];\n loadViteConfig?: boolean;\n discoverExternalPackages?: boolean;\n includeExternalBaseClasses?: boolean;\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n\n // Output Configuration\n /** Explicit output ownership; omitted retains legacy filename inference. */\n outputMode?: 'dev' | 'build';\n outputDir?: string;\n outputName?: string;\n generateTypeStub?: boolean;\n stubName?: string;\n\n // Metadata\n injectPackageInfo?: boolean;\n moduleType?: string;\n /** Test artifacts are local inputs, never dependency discovery providers. */\n artifactPurpose?: 'runtime' | 'test';\n\n // Tree Shaking (External Object Filtering)\n /**\n * @deprecated Tree shaking is no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n treeShake?: boolean;\n\n /**\n * @deprecated Whitelists are no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n externalObjectsWhitelist?: string[];\n}\n\ninterface ScannerConfig {\n baseClasses: string[];\n includePrivateMethods: boolean;\n includeStaticMethods: boolean;\n followImports: boolean;\n smrtDependencies?: string[];\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/**\n * Subset of the smrt-auto-service Vite plugin options we probe when reading\n * baseClasses out of a project's vite.config.ts.\n */\ninterface VitePluginOptions {\n baseClasses?: string[];\n followImports?: boolean;\n}\n\n/**\n * Structural shape of a loaded Vite plugin entry as probed by\n * loadViteConfigBaseClasses(). The plugin may expose its options directly, on\n * `_options`, or via an `api` object/function — all optional and discovered at\n * runtime, so each is modeled as optional here.\n */\ninterface VitePluginProbe {\n name?: string;\n options?: VitePluginOptions;\n _options?: VitePluginOptions;\n api?:\n | { options?: VitePluginOptions }\n | (() => { options?: VitePluginOptions });\n}\n\n/**\n * Orchestrates manifest generation with configurable options\n *\n * Consolidates logic from generate-manifest.js and generate-test-manifest.js\n * into a single, testable, and maintainable service.\n */\nexport class ManifestBuilder {\n constructor(private readonly projectRoot?: string) {}\n\n private root(): string {\n return this.projectRoot ?? process.cwd();\n }\n /**\n * Generate manifest with specified options\n */\n async generate(\n options: ManifestBuilderOptions = {},\n ): Promise<SmartObjectManifest> {\n // 1. Discover files to scan\n const files = await this.discoverFiles(options);\n\n if (files.length === 0) {\n logger.debug('[smrt] No source files found');\n return this.createEmptyManifest(options);\n }\n\n logger.debug(`[smrt] Scanning ${files.length} source file(s)...`);\n\n // 2. Configure scanner (baseClasses, external packages, vite config)\n const scannerConfig = await this.configureScanner(options);\n\n // 3. Scan files with OXC scanner\n const scannedManifest = await this.scanAndBuildManifest(\n scannerConfig,\n options,\n );\n\n // 4. Enrich manifest with metadata (smrtDependencies, packageInfo)\n const manifest = this.enrichManifest(\n scannedManifest,\n options,\n scannerConfig,\n );\n\n // 5. Write output files\n this.normalizeFilePaths(manifest);\n await this.writeOutput(manifest, options);\n\n return manifest;\n }\n\n private normalizeFilePaths(manifest: SmartObjectManifest): void {\n const workspaceRoot = this.findWorkspaceRoot(this.root());\n\n for (const obj of Object.values(manifest.objects || {})) {\n if (obj.filePath && isAbsolute(obj.filePath)) {\n obj.filePath = relative(workspaceRoot, obj.filePath).replace(\n /\\\\/g,\n '/',\n );\n }\n }\n }\n\n private findWorkspaceRoot(startDir: string): string {\n let current = resolve(startDir);\n\n while (true) {\n if (\n existsSync(resolve(current, 'pnpm-workspace.yaml')) ||\n existsSync(resolve(current, '.git'))\n ) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return resolve(startDir);\n }\n current = parent;\n }\n }\n\n /**\n * Discover source files based on include/exclude patterns\n */\n private async discoverFiles(\n options: ManifestBuilderOptions,\n ): Promise<string[]> {\n const include = options.include || ['src/**/*.ts'];\n const exclude = options.exclude || ['src/**/*.d.ts', 'node_modules/**'];\n const { discoverSourceFiles } = await importScanner();\n return discoverSourceFiles({\n cwd: this.root(),\n include,\n exclude,\n followSymbolicLinks: options.followSymbolicLinks ?? false,\n });\n }\n\n /**\n * Configure AST scanner with baseClasses, external packages, and vite config\n */\n private async configureScanner(\n options: ManifestBuilderOptions,\n ): Promise<ScannerConfig> {\n let baseClasses = options.baseClasses || [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n ];\n\n // Load from vite.config.ts if requested\n if (options.loadViteConfig) {\n const viteBaseClasses = await this.loadViteConfigBaseClasses();\n if (viteBaseClasses && viteBaseClasses.length > 0) {\n baseClasses = viteBaseClasses;\n logger.debug(\n `[smrt] Using baseClasses from vite.config.ts: ${baseClasses.join(', ')}`,\n );\n }\n }\n\n // Discover external SMRT packages\n let smrtDependencies: string[] = [];\n if (options.discoverExternalPackages) {\n logger.debug('[smrt] Discovering external SMRT packages...');\n smrtDependencies = discoverSmrtPackages({ baseDir: this.root() });\n logger.debug(\n `[smrt] Found ${smrtDependencies.length} SMRT package(s): ${smrtDependencies.join(', ')}`,\n );\n\n // Optionally add external base classes\n if (options.includeExternalBaseClasses) {\n const externalClasses =\n await this.loadExternalBaseClasses(smrtDependencies);\n if (externalClasses.length > 0) {\n baseClasses = [...baseClasses, ...externalClasses];\n logger.debug(\n `[smrt] Added ${externalClasses.length} external base class(es) to scanner`,\n );\n logger.debug(`[smrt] Total baseClasses: ${baseClasses.length}`);\n }\n }\n }\n\n return {\n baseClasses,\n includePrivateMethods: options.includePrivateMethods ?? false,\n includeStaticMethods: options.includeStaticMethods ?? true,\n followImports: options.followImports ?? false,\n smrtDependencies,\n };\n }\n\n /**\n * Scan files using OxcScanner and build manifest via ManifestAdapter.\n *\n * This replaces the old ASTScanner-based scanFiles() method.\n * Uses the same OXC pipeline as the Vite plugin's scanWithOxc().\n */\n private async scanAndBuildManifest(\n config: ScannerConfig,\n options: ManifestBuilderOptions,\n ): Promise<SmartObjectManifest> {\n const { OxcScanner, ManifestAdapter } = await importScanner();\n\n const scanner = new OxcScanner({\n cwd: this.root(),\n include: options.include || ['src/**/*.ts'],\n exclude: options.exclude || ['src/**/*.d.ts', 'node_modules/**'],\n baseClasses: config.baseClasses,\n includePrivateMethods: config.includePrivateMethods,\n includeStaticMethods: config.includeStaticMethods,\n followImports: config.followImports,\n followSymbolicLinks: options.followSymbolicLinks,\n });\n\n const { results, resolved } = await scanner.scanAndResolve();\n const scanErrors = results.errors.filter(\n (diagnostic) => diagnostic.severity === 'error',\n );\n if (scanErrors.length > 0) {\n const detail = scanErrors\n .map((diagnostic) => {\n const where = diagnostic.line\n ? `${diagnostic.filePath}:${diagnostic.line}`\n : diagnostic.filePath;\n return `${where} — ${diagnostic.message}`;\n })\n .join('; ');\n throw new Error(\n `[smrt] Build aborted due to ${scanErrors.length} scan error(s): ${detail}`,\n );\n }\n\n // Read package.json for metadata\n let packageName: string | undefined;\n let packageVersion: string | undefined;\n let packageJson: { name?: string; version?: string } | undefined;\n try {\n const pkgPath = resolve(this.root(), 'package.json');\n const pkgContent = readFileSync(pkgPath, 'utf-8');\n packageJson = JSON.parse(pkgContent);\n packageName = packageJson?.name || undefined;\n packageVersion = packageJson?.version || undefined;\n } catch {\n // package.json not found - continue without packageName\n }\n\n // Convert to manifest format\n const adapter = new ManifestAdapter();\n const manifest = adapter.toManifest(resolved, {\n packageName,\n packageVersion,\n typeAliases: results.typeAliases,\n });\n\n // Set smrtDependencies BEFORE mergeInheritedFields so external packages can be loaded\n if (config.smrtDependencies && config.smrtDependencies.length > 0) {\n manifest.smrtDependencies = config.smrtDependencies;\n }\n\n // Run THE manifest generation pass sequence (shared with\n // ManifestGenerator.generateManifest() and the Vite plugin, #2360 — this\n // path used to skip the report and conflict-key passes).\n const manifestGen = new ManifestGenerator();\n manifestGen.applyGenerationPasses(manifest, { packageName, packageJson });\n\n return manifest;\n }\n\n /**\n * Enrich manifest with metadata (moduleType, smrtDependencies, packageInfo).\n *\n * Note: As of Issue #1013, this method no longer merges external package\n * objects into the manifest. Dependencies are referenced via\n * `smrtDependencies` and resolved at runtime by manifest-loader.ts.\n */\n private enrichManifest(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n scannerConfig: ScannerConfig,\n ): SmartObjectManifest {\n // Add module type\n manifest.moduleType = options.moduleType || 'smrt';\n const purpose =\n options.artifactPurpose ??\n (options.outputName?.includes('test') ||\n options.stubName?.includes('test')\n ? 'test'\n : undefined);\n if (purpose) manifest.artifactPurpose = purpose;\n\n // Record dependency references (not their objects) so runtime\n // manifest-loader.ts can discover and load them on demand.\n if (\n scannerConfig.smrtDependencies &&\n scannerConfig.smrtDependencies.length > 0\n ) {\n manifest.smrtDependencies = scannerConfig.smrtDependencies;\n }\n\n // Inject package info\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n logger.debug(`[smrt] Injected package name: ${packageInfo.name}`);\n } else {\n logger.warn(\n '[smrt] Warning: Could not read package.json, packageName will be undefined',\n );\n }\n }\n\n return manifest;\n }\n\n /**\n * Write manifest JSON and optional TypeScript stub\n */\n private async writeOutput(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n ): Promise<void> {\n const manager = new ManifestManager(this.root());\n const isTest =\n options.outputName?.includes('test') ||\n options.stubName?.includes('test');\n const mode = options.outputMode ?? (isTest ? 'dev' : 'build');\n\n // 1. Always write to the unified location via ManifestManager\n manager.write(manifest, mode);\n\n // 2. Legacy/Explicit Output (if requested or for stubs)\n const outputDir = resolve(this.root(), options.outputDir || 'src/manifest');\n const outputName = options.outputName || 'manifest.json';\n\n // Ensure output directory exists for legacy/stubs\n mkdirSync(outputDir, { recursive: true });\n\n // Write JSON manifest to legacy location if different from unified path\n const manifestPath = resolve(outputDir, outputName);\n const unifiedPath = manager.getOutputPath(mode);\n\n if (resolve(manifestPath) !== resolve(unifiedPath)) {\n publishAtomicArtifact({\n path: manifestPath,\n content: JSON.stringify(manifest, null, 2),\n });\n }\n\n // Write TypeScript stub if requested\n if (options.generateTypeStub) {\n const stubName = options.stubName || 'manifest.ts';\n const tsContent = this.generateTypeStub(manifest, stubName);\n const stubPath = resolve(outputDir, stubName);\n writeFileSync(stubPath, tsContent);\n }\n\n const objectCount = Object.keys(manifest.objects).length;\n logger.info(`[smrt] ✅ Generated manifest with ${objectCount} object(s)`);\n logger.debug(`[smrt] Unified: ${unifiedPath}`);\n }\n\n /**\n * Generate TypeScript stub file with manifest constant\n */\n private generateTypeStub(\n manifest: SmartObjectManifest,\n filename: string,\n ): string {\n const isTestManifest = filename.includes('test');\n\n const comment = isTestManifest\n ? `/**\n * Test manifest stub\n * This file is replaced by generate-test-manifest.js during pretest\n * If tests run without pretest, this empty manifest is used\n *\n * DO NOT EDIT MANUALLY - This file is automatically generated\n */`\n : `/**\n * Auto-generated static manifest\n * Generated at build time from SMRT object scanning\n * DO NOT EDIT - This file is automatically generated\n */`;\n\n const exportName = isTestManifest ? 'testManifest' : 'staticManifest';\n\n // Use relative import for core package (to avoid self-reference during build),\n // package import for all other packages\n const isCorePackage = manifest.packageName === '@happyvertical/smrt-core';\n const importPath = isCorePackage\n ? '../scanner/types.js'\n : '@happyvertical/smrt-core/scanner/types';\n\n return `${comment}\n\nimport type { SmartObjectManifest } from '${importPath}';\n\nexport const ${exportName}: SmartObjectManifest = ${JSON.stringify(manifest, null, 2)} as const;\n\nexport default ${exportName};\n`;\n }\n\n /**\n * Load baseClasses from vite.config.ts\n */\n private async loadViteConfigBaseClasses(): Promise<string[] | null> {\n try {\n const viteConfigPath = resolve(this.root(), 'vite.config.ts');\n if (!existsSync(viteConfigPath)) {\n logger.debug('[smrt] vite.config.ts not found');\n return null;\n }\n\n logger.debug('[smrt] Found vite.config.ts, attempting to load...');\n\n // Use vite to load config which handles TypeScript\n const { loadConfigFromFile } = await importProjectVite(\n this.root(),\n 'Loading vite.config.ts',\n );\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'test' },\n viteConfigPath,\n );\n\n if (!loaded?.config?.plugins) {\n logger.debug('[smrt] No plugins found in vite config');\n return null;\n }\n\n // Vite's loaded plugins are a recursive PluginOption[] (plugins, arrays,\n // falsy values, promises). We only probe a few optional fields, so model\n // them structurally and narrow defensively instead of using `any`.\n const plugins = loaded.config.plugins as readonly VitePluginProbe[];\n\n logger.debug('[smrt] Vite config loaded', {\n plugins: plugins.map((p) => p?.name),\n });\n\n // Find smrtPlugin in plugins array\n const smrtPlugin = plugins.find((p) => p?.name === 'smrt-auto-service');\n\n if (!smrtPlugin) {\n logger.debug(\n '[smrt] smrt-auto-service plugin not found in plugins array',\n );\n return null;\n }\n\n logger.debug('[smrt] Found smrt-auto-service plugin');\n\n // Try different ways to access options\n const api = smrtPlugin.api;\n let opts: VitePluginOptions | undefined =\n (typeof api === 'object' && api !== null ? api.options : undefined) ||\n smrtPlugin.options ||\n smrtPlugin._options;\n\n if (!opts && typeof api === 'function') {\n try {\n opts = api().options;\n } catch (e) {\n logger.debug('[smrt] Could not call plugin.api()');\n }\n }\n\n if (opts?.baseClasses) {\n logger.debug('[smrt] Plugin options', {\n baseClasses: opts.baseClasses,\n followImports: opts.followImports,\n });\n return opts.baseClasses;\n }\n\n logger.debug('[smrt] Could not access plugin options');\n return null;\n } catch (error) {\n logger.warn('[smrt] Error loading vite.config.ts', {\n error: error instanceof Error ? error.message : String(error),\n });\n logger.debug('[smrt] Using default baseClasses');\n return null;\n }\n }\n\n /**\n * Load base classes from external SMRT package manifests\n */\n private async loadExternalBaseClasses(packages: string[]): Promise<string[]> {\n const externalBaseClasses: string[] = [];\n\n for (const pkgName of packages) {\n // Resolve the manifest the same way discovery does (honors `.smrt/` and\n // `src/manifest/`, not just `dist/`) so source-only workspace packages\n // still contribute base classes (#1378).\n const manifestPath = resolveManifestPath(pkgName, this.root());\n if (!manifestPath) {\n logger.debug(`[smrt] ${pkgName}: no SMRT manifest resolved`);\n continue;\n }\n\n try {\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest: SmartObjectManifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n const foundClassNames: string[] = [];\n for (const objDef of Object.values(manifest.objects)) {\n if (objDef.className) {\n const className = objDef.className;\n foundClassNames.push(className);\n externalBaseClasses.push(className);\n }\n }\n\n logger.debug(\n `[smrt] ${pkgName}: found ${foundClassNames.length} class(es) - ${foundClassNames.join(', ')}`,\n );\n } catch (error) {\n logger.debug(\n `[smrt] ${pkgName}: manifest not found or invalid - ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return externalBaseClasses;\n }\n\n /**\n * Read package.json from current working directory\n */\n private readPackageJson(): PackageInfo {\n try {\n const packageJsonPath = resolve(this.root(), 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return {\n name: packageJson.name,\n version: packageJson.version,\n };\n } catch (error) {\n return {};\n }\n }\n\n /**\n * Create empty manifest when no files found\n */\n private createEmptyManifest(\n options: ManifestBuilderOptions,\n ): SmartObjectManifest {\n const manifest: SmartObjectManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n moduleType: options.moduleType || 'smrt',\n ...(options.artifactPurpose\n ? { artifactPurpose: options.artifactPurpose }\n : {}),\n };\n\n if (options.discoverExternalPackages) {\n manifest.smrtDependencies = discoverSmrtPackages({\n baseDir: this.root(),\n });\n }\n\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n }\n }\n\n return manifest;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manifest/manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manifest/manager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAI3E;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IACd,OAAO,CAAC,WAAW;gBAAX,WAAW,EAAE,MAAM;IAEvC;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM;IAUvD;;;OAGG;IACH,SAAS,IAAI,mBAAmB,GAAG,IAAI;IAQvC;;;;;;;;OAQG;IACH,sBAAsB,IAAI,mBAAmB,GAAG,IAAI;IAQpD,OAAO,CAAC,cAAc;IAiBtB;;OAEG;IACH,KAAK,CAAC,QAAQ,EAAE,mBAAmB,EAAE,IAAI,GAAE,KAAK,GAAG,OAAe,GAAG,IAAI;IAczE;;OAEG;IACG,uBAAuB,CAC3B,WAAW,EAAE,UAAU,EAAE,EACzB,OAAO,EAAE;QACP,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC7B,GACA,OAAO,CAAC,mBAAmB,CAAC;CAMhC"}
|
package/dist/manifest/manager.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { publishAtomicArtifact } from "../consumer-plugin/artifact-publication.js";
|
|
1
2
|
import { ManifestGenerator } from "../scanner/manifest-generator.js";
|
|
2
3
|
import { createLogger } from "@happyvertical/logger";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
5
6
|
//#region src/manifest/manager.ts
|
|
6
7
|
var logger = createLogger({ level: "info" });
|
|
@@ -71,7 +72,10 @@ var ManifestManager = class {
|
|
|
71
72
|
const outputPath = this.getOutputPath(mode);
|
|
72
73
|
const outputDir = dirname(outputPath);
|
|
73
74
|
if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
|
|
74
|
-
|
|
75
|
+
publishAtomicArtifact({
|
|
76
|
+
path: outputPath,
|
|
77
|
+
content: JSON.stringify(manifest, null, 2)
|
|
78
|
+
});
|
|
75
79
|
}
|
|
76
80
|
/**
|
|
77
81
|
* Generates a manifest from scan results and writes it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","names":[],"sources":["../../src/manifest/manager.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync
|
|
1
|
+
{"version":3,"file":"manager.js","names":[],"sources":["../../src/manifest/manager.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { createLogger } from '@happyvertical/logger';\nimport { publishAtomicArtifact } from '../consumer-plugin/artifact-publication.js';\nimport { ManifestGenerator } from '../scanner/manifest-generator.js';\nimport type { ScanResult, SmartObjectManifest } from '../scanner/types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Reads, writes, and generates SMRT manifests for a project.\n *\n * @remarks\n * Manifests are JSON files containing class/field metadata produced by the AST scanner.\n * Dev manifests live at `.smrt/manifest.json`; build manifests at `dist/manifest.json`.\n * Workspace packages may also provide a checked-in source manifest at\n * `src/manifest/manifest.json`. When loading a local project, the dev path takes\n * priority; for external packages (dependencies), the build path takes priority\n * to avoid pulling in test objects.\n */\nexport class ManifestManager {\n constructor(private projectRoot: string) {}\n\n /**\n * Get the standard output path for the manifest based on mode.\n */\n getOutputPath(mode: 'dev' | 'build' | 'source'): string {\n if (mode === 'dev') {\n return join(this.projectRoot, '.smrt/manifest.json');\n }\n if (mode === 'source') {\n return join(this.projectRoot, 'src/manifest/manifest.json');\n }\n return join(this.projectRoot, 'dist/manifest.json');\n }\n\n /**\n * Loads the local manifest from the standard locations.\n * Priority: .smrt/manifest.json -> dist/manifest.json -> src/manifest/manifest.json\n */\n loadLocal(): SmartObjectManifest | null {\n return this._loadFromPaths([\n this.getOutputPath('dev'),\n this.getOutputPath('build'),\n this.getOutputPath('source'),\n ]);\n }\n\n /**\n * Loads the manifest for an external package (dependency).\n * Priority: dist/manifest.json -> .smrt/manifest.json -> src/manifest/manifest.json\n *\n * External packages should prefer their production (dist) manifest\n * over the dev/test (.smrt) manifest to avoid pulling in test objects\n * and transitive dependencies. In a workspace, the checked-in source manifest\n * provides the same package metadata even when the sibling package has not been built yet.\n */\n loadForExternalPackage(): SmartObjectManifest | null {\n return this._loadFromPaths([\n this.getOutputPath('build'),\n this.getOutputPath('dev'),\n this.getOutputPath('source'),\n ]);\n }\n\n private _loadFromPaths(paths: string[]): SmartObjectManifest | null {\n for (const path of paths) {\n if (existsSync(path)) {\n try {\n const content = readFileSync(path, 'utf-8');\n return JSON.parse(content);\n } catch (error) {\n logger.error(`[ManifestManager] Failed to read manifest at ${path}`, {\n error,\n });\n }\n }\n }\n\n return null;\n }\n\n /**\n * Writes a manifest to the standard location.\n */\n write(manifest: SmartObjectManifest, mode: 'dev' | 'build' = 'dev'): void {\n const outputPath = this.getOutputPath(mode);\n const outputDir = dirname(outputPath);\n\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n\n publishAtomicArtifact({\n path: outputPath,\n content: JSON.stringify(manifest, null, 2),\n });\n }\n\n /**\n * Generates a manifest from scan results and writes it.\n */\n async generateFromScanResults(\n scanResults: ScanResult[],\n options: {\n mode: 'dev' | 'build';\n packageName?: string;\n packageVersion?: string;\n packageJson?: Record<string, unknown>;\n smrtDependencies?: string[];\n },\n ): Promise<SmartObjectManifest> {\n const generator = new ManifestGenerator();\n const manifest = generator.generateManifest(scanResults, options);\n this.write(manifest, options.mode);\n return manifest;\n }\n}\n"],"mappings":";;;;;;AAOA,IAAM,SAAS,aAAa,EAAE,OAAO,OAAO,CAAC;;;;;;;;;;;;AAa7C,IAAa,kBAAb,MAA6B;CACP;CAApB,YAAY,aAA6B;EAArB,KAAA,cAAA;CAAsB;;;;CAK1C,cAAc,MAA0C;EACtD,IAAI,SAAS,OACX,OAAO,KAAK,KAAK,aAAa,qBAAqB;EAErD,IAAI,SAAS,UACX,OAAO,KAAK,KAAK,aAAa,4BAA4B;EAE5D,OAAO,KAAK,KAAK,aAAa,oBAAoB;CACpD;;;;;CAMA,YAAwC;EACtC,OAAO,KAAK,eAAe;GACzB,KAAK,cAAc,KAAK;GACxB,KAAK,cAAc,OAAO;GAC1B,KAAK,cAAc,QAAQ;EAC7B,CAAC;CACH;;;;;;;;;;CAWA,yBAAqD;EACnD,OAAO,KAAK,eAAe;GACzB,KAAK,cAAc,OAAO;GAC1B,KAAK,cAAc,KAAK;GACxB,KAAK,cAAc,QAAQ;EAC7B,CAAC;CACH;CAEA,eAAuB,OAA6C;EAClE,KAAK,MAAM,QAAQ,OACjB,IAAI,WAAW,IAAI,GACjB,IAAI;GACF,MAAM,UAAU,aAAa,MAAM,OAAO;GAC1C,OAAO,KAAK,MAAM,OAAO;EAC3B,SAAS,OAAO;GACd,OAAO,MAAM,gDAAgD,QAAQ,EACnE,MACF,CAAC;EACH;EAIJ,OAAO;CACT;;;;CAKA,MAAM,UAA+B,OAAwB,OAAa;EACxE,MAAM,aAAa,KAAK,cAAc,IAAI;EAC1C,MAAM,YAAY,QAAQ,UAAU;EAEpC,IAAI,CAAC,WAAW,SAAS,GACvB,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;EAG1C,sBAAsB;GACpB,MAAM;GACN,SAAS,KAAK,UAAU,UAAU,MAAM,CAAC;EAC3C,CAAC;CACH;;;;CAKA,MAAM,wBACJ,aACA,SAO8B;EAE9B,MAAM,WAAW,IADK,kBACL,CAAA,CAAU,iBAAiB,aAAa,OAAO;EAChE,KAAK,MAAM,UAAU,QAAQ,IAAI;EACjC,OAAO;CACT;AACF"}
|