@kjerneverk/riotplan-catalyst 1.0.0-dev.0 → 1.0.4
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/.github/workflows/npm-publish.yml +65 -0
- package/.github/workflows/test.yml +42 -0
- package/README.md +3 -0
- package/eslint.config.mjs +84 -37
- package/package.json +13 -7
- package/src/loader/catalyst-loader.ts +142 -141
- package/src/loader/plan-manifest.ts +85 -85
- package/src/merger/facet-merger.ts +87 -87
- package/src/riotplan-catalyst.ts +38 -38
- package/src/schema/schemas.ts +57 -57
- package/tsconfig.json +10 -8
- package/vite.config.ts +30 -39
- package/vitest.config.ts +28 -23
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: Node.js Package (npm)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- working
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write
|
|
12
|
+
packages: write
|
|
13
|
+
id-token: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
publish-npm:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 15
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 24
|
|
24
|
+
registry-url: https://registry.npmjs.org/
|
|
25
|
+
- name: Determine npm tag and publish strategy
|
|
26
|
+
id: npm-tag
|
|
27
|
+
run: |
|
|
28
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
29
|
+
IS_RELEASE="${{ github.event_name == 'release' }}"
|
|
30
|
+
SHORT_SHA=$(git rev-parse --short=7 HEAD)
|
|
31
|
+
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
|
32
|
+
|
|
33
|
+
if [[ "$VERSION" == *"-"* ]]; then
|
|
34
|
+
echo "tag=dev" >> $GITHUB_OUTPUT
|
|
35
|
+
echo "should_publish=true" >> $GITHUB_OUTPUT
|
|
36
|
+
# Add timestamp and SHA to pre-release version (e.g., 1.5.5-dev.0 -> 1.5.5-dev.20260131210612.ab169e2)
|
|
37
|
+
BASE_VERSION="${VERSION%%-*}"
|
|
38
|
+
NEW_VERSION="${BASE_VERSION}-dev.${TIMESTAMP}.${SHORT_SHA}"
|
|
39
|
+
echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT
|
|
40
|
+
echo "📦 Publishing pre-release version: ${NEW_VERSION}"
|
|
41
|
+
else
|
|
42
|
+
echo "tag=latest" >> $GITHUB_OUTPUT
|
|
43
|
+
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
44
|
+
# Only publish production versions on release events
|
|
45
|
+
if [[ "$IS_RELEASE" == "true" ]]; then
|
|
46
|
+
echo "should_publish=true" >> $GITHUB_OUTPUT
|
|
47
|
+
echo "📦 Publishing production version: ${VERSION}"
|
|
48
|
+
else
|
|
49
|
+
echo "should_publish=false" >> $GITHUB_OUTPUT
|
|
50
|
+
echo "⚠️ Skipping publish: Production version detected on non-release push"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
- run: npm install -g npm@latest
|
|
54
|
+
if: steps.npm-tag.outputs.should_publish == 'true'
|
|
55
|
+
- run: npm install --verbose --foreground-scripts
|
|
56
|
+
if: steps.npm-tag.outputs.should_publish == 'true'
|
|
57
|
+
timeout-minutes: 10
|
|
58
|
+
- name: Update package.json version for pre-release
|
|
59
|
+
if: steps.npm-tag.outputs.should_publish == 'true' && steps.npm-tag.outputs.tag == 'dev'
|
|
60
|
+
run: |
|
|
61
|
+
node -e "const pkg = require('./package.json'); pkg.version = '${{ steps.npm-tag.outputs.version }}'; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');"
|
|
62
|
+
echo "Updated package.json to version ${{ steps.npm-tag.outputs.version }}"
|
|
63
|
+
- name: Publish to npm
|
|
64
|
+
if: steps.npm-tag.outputs.should_publish == 'true'
|
|
65
|
+
run: npm publish --access public --tag ${{ steps.npm-tag.outputs.tag }}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Run Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- working
|
|
8
|
+
- 'release/**'
|
|
9
|
+
- 'feature/**'
|
|
10
|
+
- 'dependabot/**'
|
|
11
|
+
pull_request:
|
|
12
|
+
branches:
|
|
13
|
+
- main
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: test-${{ github.head_ref || github.ref_name }}
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
contents: write
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
test:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
timeout-minutes: 15
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: actions/setup-node@v4
|
|
29
|
+
with:
|
|
30
|
+
node-version: 24
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: |
|
|
33
|
+
# Use public npm registry (package-lock.json is gitignored)
|
|
34
|
+
npm config set registry https://registry.npmjs.org/
|
|
35
|
+
# Clean install to ensure platform-specific optional deps (rollup native bindings) are resolved correctly
|
|
36
|
+
# See: https://github.com/npm/cli/issues/4828
|
|
37
|
+
rm -rf node_modules package-lock.json
|
|
38
|
+
npm install --force
|
|
39
|
+
timeout-minutes: 10
|
|
40
|
+
- run: npm run lint
|
|
41
|
+
- run: npm run build
|
|
42
|
+
- run: npm run test
|
package/README.md
CHANGED
package/eslint.config.mjs
CHANGED
|
@@ -1,38 +1,85 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
|
3
|
+
import importPlugin from "eslint-plugin-import";
|
|
4
|
+
import globals from "globals";
|
|
5
|
+
import tsParser from "@typescript-eslint/parser";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import js from "@eslint/js";
|
|
9
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
const compat = new FlatCompat({
|
|
14
|
+
baseDirectory: __dirname,
|
|
15
|
+
recommendedConfig: js.configs.recommended,
|
|
16
|
+
allConfig: js.configs.all
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores([
|
|
21
|
+
"dist/**",
|
|
22
|
+
"node_modules/**",
|
|
23
|
+
"**/*.test.ts",
|
|
24
|
+
]),
|
|
25
|
+
{
|
|
26
|
+
extends: compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
|
|
27
|
+
|
|
28
|
+
plugins: {
|
|
29
|
+
"@typescript-eslint": typescriptEslint,
|
|
30
|
+
"import": importPlugin,
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
languageOptions: {
|
|
34
|
+
globals: {
|
|
35
|
+
...globals.node,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
parser: tsParser,
|
|
39
|
+
ecmaVersion: "latest",
|
|
40
|
+
sourceType: "module",
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
rules: {
|
|
44
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
45
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
46
|
+
|
|
47
|
+
"@typescript-eslint/no-unused-vars": ["warn", {
|
|
48
|
+
argsIgnorePattern: "^_",
|
|
49
|
+
}],
|
|
50
|
+
|
|
51
|
+
indent: ["error", 4, {
|
|
52
|
+
SwitchCase: 1,
|
|
53
|
+
}],
|
|
54
|
+
|
|
55
|
+
"import/extensions": ["error", "ignorePackages", {
|
|
56
|
+
"js": "always",
|
|
57
|
+
"ts": "never"
|
|
58
|
+
}],
|
|
59
|
+
|
|
60
|
+
"import/no-extraneous-dependencies": ["error", {
|
|
61
|
+
devDependencies: true,
|
|
62
|
+
optionalDependencies: false,
|
|
63
|
+
peerDependencies: false,
|
|
64
|
+
}],
|
|
65
|
+
|
|
66
|
+
"no-console": ["error"],
|
|
67
|
+
|
|
68
|
+
"no-restricted-imports": ["error", {
|
|
69
|
+
paths: ["dayjs", "fs", "moment-timezone"],
|
|
70
|
+
patterns: [
|
|
71
|
+
{
|
|
72
|
+
group: ["src/**"],
|
|
73
|
+
message: "Use absolute imports instead of relative imports"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}]
|
|
77
|
+
},
|
|
36
78
|
},
|
|
37
|
-
|
|
38
|
-
|
|
79
|
+
// Allow console in CLI files
|
|
80
|
+
{
|
|
81
|
+
files: ["src/cli/**/*.ts"],
|
|
82
|
+
rules: {
|
|
83
|
+
"no-console": "off",
|
|
84
|
+
},
|
|
85
|
+
}]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kjerneverk/riotplan-catalyst",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Catalyst system for RiotPlan - composable, layerable guidance packages for plan creation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/riotplan-catalyst.js",
|
|
@@ -30,13 +30,15 @@
|
|
|
30
30
|
"zod": "^3.23.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@
|
|
33
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
34
|
+
"@eslint/js": "^9.28.0",
|
|
34
35
|
"@types/node": "^25.2.2",
|
|
35
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
36
|
-
"@typescript-eslint/parser": "^
|
|
36
|
+
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
|
37
|
+
"@typescript-eslint/parser": "^8.34.0",
|
|
37
38
|
"@vitest/coverage-v8": "^1.1.0",
|
|
38
|
-
"eslint": "^
|
|
39
|
-
"eslint-plugin-import": "^2.
|
|
39
|
+
"eslint": "^9.28.0",
|
|
40
|
+
"eslint-plugin-import": "^2.31.0",
|
|
41
|
+
"globals": "^17.0.0",
|
|
40
42
|
"typescript": "^5.4.0",
|
|
41
43
|
"vite": "^5.1.0",
|
|
42
44
|
"vite-plugin-dts": "^1.0.0",
|
|
@@ -50,5 +52,9 @@
|
|
|
50
52
|
"ai"
|
|
51
53
|
],
|
|
52
54
|
"author": "Kjerneverk",
|
|
53
|
-
"license": "Apache-2.0"
|
|
55
|
+
"license": "Apache-2.0",
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "https://github.com/kjerneverk/riotplan-catalyst"
|
|
59
|
+
}
|
|
54
60
|
}
|
|
@@ -7,18 +7,18 @@ import { readFile, readdir, stat } from 'node:fs/promises';
|
|
|
7
7
|
import { join, resolve } from 'node:path';
|
|
8
8
|
import { parse as parseYaml } from 'yaml';
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from '@/schema/schemas';
|
|
10
|
+
CatalystManifestSchema,
|
|
11
|
+
FACET_DIRECTORIES,
|
|
12
|
+
type FacetType,
|
|
13
|
+
} from '@/schema/schemas.js';
|
|
14
14
|
import type {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from '@/types';
|
|
15
|
+
Catalyst,
|
|
16
|
+
CatalystManifest,
|
|
17
|
+
CatalystFacets,
|
|
18
|
+
FacetContent,
|
|
19
|
+
CatalystLoadOptions,
|
|
20
|
+
CatalystLoadResult,
|
|
21
|
+
} from '@/types.js';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Load a catalyst manifest from catalyst.yml
|
|
@@ -27,29 +27,29 @@ import type {
|
|
|
27
27
|
* @throws Error if manifest is missing or invalid
|
|
28
28
|
*/
|
|
29
29
|
async function loadManifest(directoryPath: string): Promise<CatalystManifest> {
|
|
30
|
-
|
|
30
|
+
const manifestPath = join(directoryPath, 'catalyst.yml');
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
try {
|
|
33
|
+
const content = await readFile(manifestPath, 'utf-8');
|
|
34
|
+
const parsed = parseYaml(content);
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// Validate with Zod schema
|
|
37
|
+
const result = CatalystManifestSchema.safeParse(parsed);
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
if (!result.success) {
|
|
40
|
+
const errors = result.error.issues.map(issue =>
|
|
41
|
+
`${issue.path.join('.')}: ${issue.message}`
|
|
42
|
+
).join('; ');
|
|
43
|
+
throw new Error(`Invalid catalyst manifest: ${errors}`);
|
|
44
|
+
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
return result.data;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
49
|
+
throw new Error(`Catalyst manifest not found at ${manifestPath}`);
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
50
52
|
}
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
@@ -58,43 +58,43 @@ async function loadManifest(directoryPath: string): Promise<CatalystManifest> {
|
|
|
58
58
|
* @returns Array of FacetContent objects
|
|
59
59
|
*/
|
|
60
60
|
async function loadFacetFiles(facetPath: string): Promise<FacetContent[]> {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
try {
|
|
62
|
+
const entries = await readdir(facetPath, { withFileTypes: true });
|
|
63
|
+
const markdownFiles = entries.filter(
|
|
64
|
+
entry => entry.isFile() && entry.name.endsWith('.md')
|
|
65
|
+
);
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
const contents = await Promise.all(
|
|
68
|
+
markdownFiles.map(async (file) => {
|
|
69
|
+
const filePath = join(facetPath, file.name);
|
|
70
|
+
const content = await readFile(filePath, 'utf-8');
|
|
71
|
+
return {
|
|
72
|
+
filename: file.name,
|
|
73
|
+
content,
|
|
74
|
+
};
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
return contents;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
81
|
+
// Directory doesn't exist - return empty array
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
83
85
|
}
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
89
|
* Check if a directory exists
|
|
90
90
|
*/
|
|
91
91
|
async function directoryExists(path: string): Promise<boolean> {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
try {
|
|
93
|
+
const stats = await stat(path);
|
|
94
|
+
return stats.isDirectory();
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/**
|
|
@@ -105,46 +105,46 @@ async function directoryExists(path: string): Promise<boolean> {
|
|
|
105
105
|
* @returns Loaded facets and any warnings
|
|
106
106
|
*/
|
|
107
107
|
async function loadFacets(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
directoryPath: string,
|
|
109
|
+
manifest: CatalystManifest,
|
|
110
|
+
options: CatalystLoadOptions = {}
|
|
111
111
|
): Promise<{ facets: CatalystFacets; warnings: string[] }> {
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
const facets: CatalystFacets = {};
|
|
113
|
+
const warnings: string[] = [];
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
// Load each facet type
|
|
116
|
+
for (const [facetKey, dirName] of Object.entries(FACET_DIRECTORIES)) {
|
|
117
|
+
const facetType = facetKey as FacetType;
|
|
118
|
+
const facetPath = join(directoryPath, dirName);
|
|
119
|
+
const exists = await directoryExists(facetPath);
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
// Check if facet is declared in manifest
|
|
122
|
+
const declared = manifest.facets?.[facetType];
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
if (exists) {
|
|
125
|
+
// Load the facet content
|
|
126
|
+
const content = await loadFacetFiles(facetPath);
|
|
127
|
+
if (content.length > 0) {
|
|
128
|
+
facets[facetType] = content;
|
|
129
|
+
}
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
131
|
+
// Warn if present but explicitly declared as false
|
|
132
|
+
if (declared === false && options.warnOnUndeclaredFacets) {
|
|
133
|
+
warnings.push(
|
|
134
|
+
`Facet '${facetType}' is present but declared as false in manifest`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
// Warn if declared but missing
|
|
139
|
+
if (declared === true && options.warnOnMissingFacets) {
|
|
140
|
+
warnings.push(
|
|
141
|
+
`Facet '${facetType}' is declared in manifest but directory '${dirName}' not found`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
144
145
|
}
|
|
145
|
-
}
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
return { facets, warnings };
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
@@ -166,35 +166,36 @@ async function loadFacets(
|
|
|
166
166
|
* ```
|
|
167
167
|
*/
|
|
168
168
|
export async function loadCatalyst(
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
directoryPath: string,
|
|
170
|
+
options: CatalystLoadOptions = {}
|
|
171
171
|
): Promise<Catalyst> {
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
// Resolve to absolute path
|
|
173
|
+
const absolutePath = resolve(directoryPath);
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
// Check if directory exists
|
|
176
|
+
if (!await directoryExists(absolutePath)) {
|
|
177
|
+
throw new Error(`Catalyst directory not found: ${absolutePath}`);
|
|
178
|
+
}
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
// Load and validate manifest
|
|
181
|
+
const manifest = await loadManifest(absolutePath);
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
// Load all facets
|
|
184
|
+
const { facets, warnings } = await loadFacets(absolutePath, manifest, options);
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
// Log warnings if any
|
|
187
|
+
if (warnings.length > 0 && !options.strict) {
|
|
188
|
+
for (const warning of warnings) {
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
190
|
+
console.warn(`[catalyst-loader] ${warning}`);
|
|
191
|
+
}
|
|
190
192
|
}
|
|
191
|
-
}
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
return {
|
|
195
|
+
manifest,
|
|
196
|
+
facets,
|
|
197
|
+
directoryPath: absolutePath,
|
|
198
|
+
};
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
/**
|
|
@@ -207,21 +208,21 @@ export async function loadCatalyst(
|
|
|
207
208
|
* @returns Result object with success/error
|
|
208
209
|
*/
|
|
209
210
|
export async function loadCatalystSafe(
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
directoryPath: string,
|
|
212
|
+
options: CatalystLoadOptions = {}
|
|
212
213
|
): Promise<CatalystLoadResult> {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
214
|
+
try {
|
|
215
|
+
const catalyst = await loadCatalyst(directoryPath, options);
|
|
216
|
+
return {
|
|
217
|
+
success: true,
|
|
218
|
+
catalyst,
|
|
219
|
+
};
|
|
220
|
+
} catch (error) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
error: error instanceof Error ? error.message : String(error),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
/**
|
|
@@ -236,26 +237,26 @@ export async function loadCatalystSafe(
|
|
|
236
237
|
* @returns Array of loaded catalysts
|
|
237
238
|
*/
|
|
238
239
|
export async function resolveCatalysts(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
identifiers: string[],
|
|
241
|
+
basePath: string = process.cwd(),
|
|
242
|
+
options: CatalystLoadOptions = {}
|
|
242
243
|
): Promise<Catalyst[]> {
|
|
243
|
-
|
|
244
|
+
const catalysts: Catalyst[] = [];
|
|
244
245
|
|
|
245
|
-
|
|
246
|
+
for (const identifier of identifiers) {
|
|
246
247
|
// Phase 1: treat identifier as a path
|
|
247
248
|
// If it's relative, resolve from basePath
|
|
248
|
-
|
|
249
|
+
const path = resolve(basePath, identifier);
|
|
249
250
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
251
|
+
try {
|
|
252
|
+
const catalyst = await loadCatalyst(path, options);
|
|
253
|
+
catalysts.push(catalyst);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Failed to load catalyst '${identifier}': ${error instanceof Error ? error.message : String(error)}`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
257
259
|
}
|
|
258
|
-
}
|
|
259
260
|
|
|
260
|
-
|
|
261
|
+
return catalysts;
|
|
261
262
|
}
|