Compare commits
2 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
73d49cb954 | |
|
|
f5989a162b |
|
|
@ -26,7 +26,7 @@ import { loadYarnProject } from 'yarn-spinner-loader';
|
|||
const result = await loadYarnProject('path/to/project.yarnproject');
|
||||
|
||||
console.log(result.project.projectName);
|
||||
console.log(result.yarnFiles); // Array of parsed Yarn documents
|
||||
console.log(result.program); // Merged IRProgram from all .yarn files
|
||||
```
|
||||
|
||||
### With esbuild
|
||||
|
|
@ -103,11 +103,7 @@ Load and compile a `.yarnproject` file and all its referenced `.yarn` files.
|
|||
interface LoadResult {
|
||||
project: YarnProject;
|
||||
baseDir: string;
|
||||
yarnFiles: Array<{
|
||||
relativePath: string;
|
||||
absolutePath: string;
|
||||
document: YarnDocument;
|
||||
}>;
|
||||
program: IRProgram;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "yarn-spinner-loader",
|
||||
"version": "0.1.1",
|
||||
"version": "0.2.0",
|
||||
"description": "Load and compile Yarn Spinner project files for various build tools",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export { YarnRunner } from './yarn-spinner/runtime/runner';
|
|||
export type {
|
||||
LoadOptions,
|
||||
LoadResult,
|
||||
LoadedYarnFile,
|
||||
IRProgram,
|
||||
YarnProject,
|
||||
SchemaValidationError,
|
||||
RunnerOptions,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export { YarnRunner } from './yarn-spinner/runtime/runner';
|
|||
export type {
|
||||
LoadOptions,
|
||||
LoadResult,
|
||||
LoadedYarnFile,
|
||||
IRProgram,
|
||||
YarnProject,
|
||||
SchemaValidationError,
|
||||
RunnerOptions,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { dirname, resolve, relative } from 'node:path';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import fg from 'fast-glob';
|
||||
import type { YarnProject } from './types';
|
||||
import { validateYarnProject, type SchemaValidationError } from './validator';
|
||||
import { parseYarn } from '../yarn-spinner/parse/parser';
|
||||
import type { YarnDocument } from '../yarn-spinner/model/ast';
|
||||
import { compile } from '../yarn-spinner/compile/compiler';
|
||||
import type { IRProgram } from '../yarn-spinner/compile/ir';
|
||||
|
||||
/**
|
||||
* Options for loading a Yarn project
|
||||
|
|
@ -14,18 +15,6 @@ export interface LoadOptions {
|
|||
baseDir?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loaded Yarn document with metadata
|
||||
*/
|
||||
export interface LoadedYarnFile {
|
||||
/** File path relative to base directory */
|
||||
relativePath: string;
|
||||
/** Absolute file path */
|
||||
absolutePath: string;
|
||||
/** Parsed Yarn document */
|
||||
document: YarnDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of loading a Yarn project
|
||||
*/
|
||||
|
|
@ -34,8 +23,8 @@ export interface LoadResult {
|
|||
project: YarnProject;
|
||||
/** Directory containing the .yarnproject file */
|
||||
baseDir: string;
|
||||
/** All loaded and parsed .yarn files */
|
||||
yarnFiles: LoadedYarnFile[];
|
||||
/** Merged IR program from all .yarn files */
|
||||
program: IRProgram;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -71,7 +60,6 @@ export async function loadYarnProject(
|
|||
projectFilePath: string,
|
||||
options: LoadOptions = {},
|
||||
): Promise<LoadResult> {
|
||||
// Resolve and read .yarnproject file
|
||||
const absoluteProjectPath = resolve(projectFilePath);
|
||||
|
||||
if (!existsSync(absoluteProjectPath)) {
|
||||
|
|
@ -87,7 +75,6 @@ export async function loadYarnProject(
|
|||
throw new LoadError(`Failed to parse .yarnproject file as JSON: ${absoluteProjectPath}`, error);
|
||||
}
|
||||
|
||||
// Validate against schema
|
||||
const validation = validateYarnProject(projectConfig);
|
||||
|
||||
if (!validation.valid) {
|
||||
|
|
@ -100,7 +87,6 @@ export async function loadYarnProject(
|
|||
const project = validation.data as YarnProject;
|
||||
const baseDir = options.baseDir || dirname(absoluteProjectPath);
|
||||
|
||||
// Find all .yarn files using glob patterns
|
||||
const sourcePatterns = project.sourceFiles;
|
||||
const ignorePatterns = project.excludeFiles || [];
|
||||
|
||||
|
|
@ -111,23 +97,21 @@ export async function loadYarnProject(
|
|||
onlyFiles: true,
|
||||
});
|
||||
|
||||
// Load and parse each .yarn file
|
||||
const yarnFiles: LoadedYarnFile[] = yarnFilePaths.map(absolutePath => {
|
||||
const relativePath = relative(baseDir, absolutePath);
|
||||
const program: IRProgram = { enums: {}, nodes: {} };
|
||||
|
||||
for (const absolutePath of yarnFilePaths) {
|
||||
const content = readFileSync(absolutePath, 'utf-8');
|
||||
const document = parseYarn(content);
|
||||
const compiled = compile(document);
|
||||
|
||||
return {
|
||||
relativePath,
|
||||
absolutePath,
|
||||
document,
|
||||
};
|
||||
});
|
||||
Object.assign(program.enums, compiled.enums);
|
||||
Object.assign(program.nodes, compiled.nodes);
|
||||
}
|
||||
|
||||
return {
|
||||
project,
|
||||
baseDir,
|
||||
yarnFiles,
|
||||
program,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +122,6 @@ export function loadYarnProjectSync(
|
|||
projectFilePath: string,
|
||||
options: LoadOptions = {},
|
||||
): LoadResult {
|
||||
// Resolve and read .yarnproject file
|
||||
const absoluteProjectPath = resolve(projectFilePath);
|
||||
|
||||
if (!existsSync(absoluteProjectPath)) {
|
||||
|
|
@ -154,7 +137,6 @@ export function loadYarnProjectSync(
|
|||
throw new LoadError(`Failed to parse .yarnproject file as JSON: ${absoluteProjectPath}`, error);
|
||||
}
|
||||
|
||||
// Validate against schema
|
||||
const validation = validateYarnProject(projectConfig);
|
||||
|
||||
if (!validation.valid) {
|
||||
|
|
@ -167,7 +149,6 @@ export function loadYarnProjectSync(
|
|||
const project = validation.data as YarnProject;
|
||||
const baseDir = options.baseDir || dirname(absoluteProjectPath);
|
||||
|
||||
// Find all .yarn files using glob patterns
|
||||
const sourcePatterns = project.sourceFiles;
|
||||
const ignorePatterns = project.excludeFiles || [];
|
||||
|
||||
|
|
@ -178,22 +159,20 @@ export function loadYarnProjectSync(
|
|||
onlyFiles: true,
|
||||
});
|
||||
|
||||
// Load and parse each .yarn file
|
||||
const yarnFiles: LoadedYarnFile[] = yarnFilePaths.map(absolutePath => {
|
||||
const relativePath = relative(baseDir, absolutePath);
|
||||
const program: IRProgram = { enums: {}, nodes: {} };
|
||||
|
||||
for (const absolutePath of yarnFilePaths) {
|
||||
const content = readFileSync(absolutePath, 'utf-8');
|
||||
const document = parseYarn(content);
|
||||
const compiled = compile(document);
|
||||
|
||||
return {
|
||||
relativePath,
|
||||
absolutePath,
|
||||
document,
|
||||
};
|
||||
});
|
||||
Object.assign(program.enums, compiled.enums);
|
||||
Object.assign(program.nodes, compiled.nodes);
|
||||
}
|
||||
|
||||
return {
|
||||
project,
|
||||
baseDir,
|
||||
yarnFiles,
|
||||
program,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ export {
|
|||
loadYarnProjectSync,
|
||||
type LoadOptions,
|
||||
type LoadResult,
|
||||
type LoadedYarnFile,
|
||||
LoadError,
|
||||
ValidationError as LoaderValidationError,
|
||||
} from './loader/index';
|
||||
|
|
|
|||
20
src/types.ts
20
src/types.ts
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import type { YarnProject } from './loader/types';
|
||||
import type { SchemaValidationError } from './loader/validator';
|
||||
import type { YarnDocument } from './yarn-spinner/model/ast';
|
||||
import type { IRProgram } from './yarn-spinner/compile/ir';
|
||||
import type { RunnerOptions } from './yarn-spinner/runtime/runner';
|
||||
import type {
|
||||
TextResult,
|
||||
|
|
@ -18,7 +18,7 @@ import type {
|
|||
export type {
|
||||
YarnProject,
|
||||
SchemaValidationError,
|
||||
YarnDocument,
|
||||
IRProgram,
|
||||
RunnerOptions,
|
||||
TextResult,
|
||||
OptionsResult,
|
||||
|
|
@ -34,18 +34,6 @@ export interface LoadOptions {
|
|||
baseDir?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loaded Yarn document with metadata
|
||||
*/
|
||||
export interface LoadedYarnFile {
|
||||
/** File path relative to base directory */
|
||||
relativePath: string;
|
||||
/** Absolute file path */
|
||||
absolutePath: string;
|
||||
/** Parsed Yarn document */
|
||||
document: YarnDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of loading a Yarn project
|
||||
*/
|
||||
|
|
@ -54,6 +42,6 @@ export interface LoadResult {
|
|||
project: YarnProject;
|
||||
/** Directory containing the .yarnproject file */
|
||||
baseDir: string;
|
||||
/** All loaded and parsed .yarn files */
|
||||
yarnFiles: LoadedYarnFile[];
|
||||
/** Merged IR program from all .yarn files */
|
||||
program: IRProgram;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ describe('loadYarnProject', () => {
|
|||
expect(result.project.projectName).toBe('Simple Test Project');
|
||||
expect(result.project.sourceFiles).toEqual(['**/*.yarn']);
|
||||
expect(result.project.baseLanguage).toBe('en');
|
||||
expect(result.yarnFiles.length).toBeGreaterThan(0);
|
||||
expect(Object.keys(result.program.nodes).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should parse all yarn files in simple project', async () => {
|
||||
it('should compile all yarn files in simple project', async () => {
|
||||
const projectPath = resolve(__dirname, 'fixtures/simple/.yarnproject');
|
||||
const result = await loadYarnProject(projectPath);
|
||||
|
||||
const titles = result.yarnFiles.flatMap(f => f.document.nodes.map(n => n.title));
|
||||
const titles = Object.keys(result.program.nodes);
|
||||
expect(titles).toContain('Start');
|
||||
expect(titles).toContain('Greeting');
|
||||
expect(titles).toContain('AnotherNode');
|
||||
|
|
@ -35,7 +35,7 @@ describe('loadYarnProject', () => {
|
|||
expect(result.project.baseLanguage).toBe('en');
|
||||
expect(result.project.localisation).toBeDefined();
|
||||
expect(result.project.localisation!['zh-Hans']).toBeDefined();
|
||||
expect(result.yarnFiles.length).toBeGreaterThan(0);
|
||||
expect(Object.keys(result.program.nodes).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should load a complex project with multiple patterns', async () => {
|
||||
|
|
@ -48,51 +48,21 @@ describe('loadYarnProject', () => {
|
|||
expect(result.project.sourceFiles).toContain('scripts/**/*.yarn');
|
||||
});
|
||||
|
||||
it('should exclude files matching excludeFiles patterns', async () => {
|
||||
const projectPath = resolve(__dirname, 'fixtures/complex/.yarnproject');
|
||||
const result = await loadYarnProject(projectPath);
|
||||
|
||||
// Check that backup file is excluded
|
||||
const backupFiles = result.yarnFiles.filter(f =>
|
||||
f.relativePath.includes('backup'),
|
||||
);
|
||||
expect(backupFiles).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should include files from multiple source patterns', async () => {
|
||||
const projectPath = resolve(__dirname, 'fixtures/complex/.yarnproject');
|
||||
const result = await loadYarnProject(projectPath);
|
||||
|
||||
const relativePaths = result.yarnFiles.map(f => f.relativePath);
|
||||
|
||||
// Should include dialogue files
|
||||
expect(relativePaths.some(p => p.includes('dialogue'))).toBe(true);
|
||||
// Should include script files
|
||||
expect(relativePaths.some(p => p.includes('scripts'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should parse yarn documents correctly', async () => {
|
||||
const projectPath = resolve(__dirname, 'fixtures/simple/.yarnproject');
|
||||
const result = await loadYarnProject(projectPath);
|
||||
|
||||
const startNode = result.yarnFiles
|
||||
.flatMap(f => f.document.nodes)
|
||||
.find(n => n.title === 'Start');
|
||||
|
||||
const startNode = result.program.nodes['Start'];
|
||||
expect(startNode).toBeDefined();
|
||||
expect(startNode!.body.length).toBeGreaterThan(0);
|
||||
expect(startNode && 'instructions' in startNode ? startNode.instructions.length : 0).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should handle nodes with tags', async () => {
|
||||
const projectPath = resolve(__dirname, 'fixtures/simple/.yarnproject');
|
||||
const result = await loadYarnProject(projectPath);
|
||||
|
||||
const greetingNode = result.yarnFiles
|
||||
.flatMap(f => f.document.nodes)
|
||||
.find(n => n.title === 'Greeting');
|
||||
|
||||
const greetingNode = result.program.nodes['Greeting'];
|
||||
expect(greetingNode).toBeDefined();
|
||||
expect(greetingNode!.nodeTags).toContain('greeting');
|
||||
});
|
||||
|
||||
it('should throw LoadError for non-existent file', async () => {
|
||||
|
|
@ -104,7 +74,6 @@ describe('loadYarnProject', () => {
|
|||
it('should throw error for invalid JSON', async () => {
|
||||
const projectPath = resolve(__dirname, 'fixtures/invalid/.yarnproject');
|
||||
|
||||
// This will fail because the fixture doesn't exist
|
||||
await expect(loadYarnProject(projectPath)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
@ -115,14 +84,14 @@ describe('loadYarnProjectSync', () => {
|
|||
const result = loadYarnProjectSync(projectPath);
|
||||
|
||||
expect(result.project.projectFileVersion).toBe(4);
|
||||
expect(result.yarnFiles.length).toBeGreaterThan(0);
|
||||
expect(Object.keys(result.program.nodes).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should parse all yarn files synchronously', () => {
|
||||
it('should compile all yarn files synchronously', () => {
|
||||
const projectPath = resolve(__dirname, 'fixtures/simple/.yarnproject');
|
||||
const result = loadYarnProjectSync(projectPath);
|
||||
|
||||
const titles = result.yarnFiles.flatMap(f => f.document.nodes.map(n => n.title));
|
||||
const titles = Object.keys(result.program.nodes);
|
||||
expect(titles).toContain('Start');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ describe('yarnSpinnerPlugin (esbuild)', () => {
|
|||
expect(result).toBeDefined();
|
||||
expect(result.contents).toContain('export default');
|
||||
expect(result.contents).toContain('project');
|
||||
expect(result.contents).toContain('yarnFiles');
|
||||
expect(result.contents).toContain('program');
|
||||
expect(result.loader).toBe('js');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ describe('yarnSpinnerRollup', () => {
|
|||
expect(typeof result).toBe('string');
|
||||
expect(result).toContain('export default');
|
||||
expect(result).toContain('project');
|
||||
expect(result).toContain('yarnFiles');
|
||||
expect(result).toContain('program');
|
||||
});
|
||||
|
||||
it('should return null for non-.yarnproject files', async () => {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ describe('yarnSpinnerVite', () => {
|
|||
expect(typeof result).toBe('string');
|
||||
expect(result).toContain('export default');
|
||||
expect(result).toContain('project');
|
||||
expect(result).toContain('yarnFiles');
|
||||
expect(result).toContain('program');
|
||||
});
|
||||
|
||||
it('should return null for non-.yarnproject files', async () => {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ describe('YarnSpinnerWebpackLoader', () => {
|
|||
expect(typeof result).toBe('string');
|
||||
expect(result).toContain('export default');
|
||||
expect(result).toContain('project');
|
||||
expect(result).toContain('yarnFiles');
|
||||
expect(result).toContain('program');
|
||||
// Ensure no errors were emitted
|
||||
expect(mockContext.emitError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue