@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.
@@ -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
@@ -325,3 +325,6 @@ if (catalyst === null) {
325
325
  ## License
326
326
 
327
327
  Apache-2.0
328
+ TEST
329
+ TEST
330
+ TEST
package/eslint.config.mjs CHANGED
@@ -1,38 +1,85 @@
1
- import js from '@eslint/js';
2
- import ts from '@typescript-eslint/eslint-plugin';
3
- import tsParser from '@typescript-eslint/parser';
4
- import importPlugin from 'eslint-plugin-import';
5
-
6
- export default [
7
- {
8
- ignores: ['dist/**', 'node_modules/**', '**/*.test.ts', 'vitest.config.ts', 'vite.config.ts'],
9
- },
10
- {
11
- files: ['**/*.ts'],
12
- languageOptions: {
13
- parser: tsParser,
14
- parserOptions: {
15
- ecmaVersion: 2024,
16
- sourceType: 'module',
17
- },
18
- globals: {
19
- console: 'readonly',
20
- process: 'readonly',
21
- NodeJS: 'readonly',
22
- },
23
- },
24
- plugins: {
25
- '@typescript-eslint': ts,
26
- import: importPlugin,
27
- },
28
- rules: {
29
- ...js.configs.recommended.rules,
30
- ...ts.configs.recommended.rules,
31
- indent: ['error', 2],
32
- quotes: ['error', 'single'],
33
- semi: ['error', 'always'],
34
- 'no-console': ['error', { allow: ['warn', 'error'] }],
35
- 'import/order': 'error',
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.0-dev.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
- "@rollup/plugin-replace": "^5.0.5",
33
+ "@eslint/eslintrc": "^3.3.1",
34
+ "@eslint/js": "^9.28.0",
34
35
  "@types/node": "^25.2.2",
35
- "@typescript-eslint/eslint-plugin": "^6.0.0",
36
- "@typescript-eslint/parser": "^6.0.0",
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": "^8.56.0",
39
- "eslint-plugin-import": "^2.29.0",
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
- CatalystManifestSchema,
11
- FACET_DIRECTORIES,
12
- type FacetType,
13
- } from '@/schema/schemas';
10
+ CatalystManifestSchema,
11
+ FACET_DIRECTORIES,
12
+ type FacetType,
13
+ } from '@/schema/schemas.js';
14
14
  import type {
15
- Catalyst,
16
- CatalystManifest,
17
- CatalystFacets,
18
- FacetContent,
19
- CatalystLoadOptions,
20
- CatalystLoadResult,
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
- const manifestPath = join(directoryPath, 'catalyst.yml');
30
+ const manifestPath = join(directoryPath, 'catalyst.yml');
31
31
 
32
- try {
33
- const content = await readFile(manifestPath, 'utf-8');
34
- const parsed = parseYaml(content);
32
+ try {
33
+ const content = await readFile(manifestPath, 'utf-8');
34
+ const parsed = parseYaml(content);
35
35
 
36
- // Validate with Zod schema
37
- const result = CatalystManifestSchema.safeParse(parsed);
36
+ // Validate with Zod schema
37
+ const result = CatalystManifestSchema.safeParse(parsed);
38
38
 
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
- }
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
- return result.data;
47
- } catch (error) {
48
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
49
- throw new Error(`Catalyst manifest not found at ${manifestPath}`);
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
- try {
62
- const entries = await readdir(facetPath, { withFileTypes: true });
63
- const markdownFiles = entries.filter(
64
- entry => entry.isFile() && entry.name.endsWith('.md')
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
- 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
- );
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
- return contents;
79
- } catch (error) {
80
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
81
- // Directory doesn't exist - return empty array
82
- return [];
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
- try {
93
- const stats = await stat(path);
94
- return stats.isDirectory();
95
- } catch {
96
- return false;
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
- directoryPath: string,
109
- manifest: CatalystManifest,
110
- options: CatalystLoadOptions = {}
108
+ directoryPath: string,
109
+ manifest: CatalystManifest,
110
+ options: CatalystLoadOptions = {}
111
111
  ): Promise<{ facets: CatalystFacets; warnings: string[] }> {
112
- const facets: CatalystFacets = {};
113
- const warnings: string[] = [];
112
+ const facets: CatalystFacets = {};
113
+ const warnings: string[] = [];
114
114
 
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);
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
- // Check if facet is declared in manifest
122
- const declared = manifest.facets?.[facetType];
121
+ // Check if facet is declared in manifest
122
+ const declared = manifest.facets?.[facetType];
123
123
 
124
- if (exists) {
125
- // Load the facet content
126
- const content = await loadFacetFiles(facetPath);
127
- if (content.length > 0) {
128
- facets[facetType] = content;
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
- // 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
- }
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
- return { facets, warnings };
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
- directoryPath: string,
170
- options: CatalystLoadOptions = {}
169
+ directoryPath: string,
170
+ options: CatalystLoadOptions = {}
171
171
  ): Promise<Catalyst> {
172
- // Resolve to absolute path
173
- const absolutePath = resolve(directoryPath);
172
+ // Resolve to absolute path
173
+ const absolutePath = resolve(directoryPath);
174
174
 
175
- // Check if directory exists
176
- if (!await directoryExists(absolutePath)) {
177
- throw new Error(`Catalyst directory not found: ${absolutePath}`);
178
- }
175
+ // Check if directory exists
176
+ if (!await directoryExists(absolutePath)) {
177
+ throw new Error(`Catalyst directory not found: ${absolutePath}`);
178
+ }
179
179
 
180
- // Load and validate manifest
181
- const manifest = await loadManifest(absolutePath);
180
+ // Load and validate manifest
181
+ const manifest = await loadManifest(absolutePath);
182
182
 
183
- // Load all facets
184
- const { facets, warnings } = await loadFacets(absolutePath, manifest, options);
183
+ // Load all facets
184
+ const { facets, warnings } = await loadFacets(absolutePath, manifest, options);
185
185
 
186
- // Log warnings if any
187
- if (warnings.length > 0 && !options.strict) {
188
- for (const warning of warnings) {
189
- console.warn(`[catalyst-loader] ${warning}`);
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
- return {
194
- manifest,
195
- facets,
196
- directoryPath: absolutePath,
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
- directoryPath: string,
211
- options: CatalystLoadOptions = {}
211
+ directoryPath: string,
212
+ options: CatalystLoadOptions = {}
212
213
  ): Promise<CatalystLoadResult> {
213
- try {
214
- const catalyst = await loadCatalyst(directoryPath, options);
215
- return {
216
- success: true,
217
- catalyst,
218
- };
219
- } catch (error) {
220
- return {
221
- success: false,
222
- error: error instanceof Error ? error.message : String(error),
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
- identifiers: string[],
240
- basePath: string = process.cwd(),
241
- options: CatalystLoadOptions = {}
240
+ identifiers: string[],
241
+ basePath: string = process.cwd(),
242
+ options: CatalystLoadOptions = {}
242
243
  ): Promise<Catalyst[]> {
243
- const catalysts: Catalyst[] = [];
244
+ const catalysts: Catalyst[] = [];
244
245
 
245
- for (const identifier of identifiers) {
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
- const path = resolve(basePath, identifier);
249
+ const path = resolve(basePath, identifier);
249
250
 
250
- try {
251
- const catalyst = await loadCatalyst(path, options);
252
- catalysts.push(catalyst);
253
- } catch (error) {
254
- throw new Error(
255
- `Failed to load catalyst '${identifier}': ${error instanceof Error ? error.message : String(error)}`
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
- return catalysts;
261
+ return catalysts;
261
262
  }