Skip to content

Commit

Permalink
chore: Rewrite presets on new architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
3y3 committed Dec 5, 2024
1 parent 513fedf commit ace3c5d
Show file tree
Hide file tree
Showing 16 changed files with 719 additions and 136 deletions.
42 changes: 41 additions & 1 deletion src/commands/build/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {Run} from '../run';
import type {BuildConfig, BuildRawConfig} from '..';

import {join} from 'node:path';
import {Mock, describe, expect, it, vi} from 'vitest';
import {when} from 'vitest-when';
import {Build} from '..';
import {handler as originalHandler} from '../handler';
import {withConfigUtils} from '~/config';
Expand Down Expand Up @@ -30,15 +32,53 @@ vi.mock('~/config', async (importOriginal) => {
};
});

export async function runBuild(args: string) {
type BuildState = {
globs?: Hash<string[]>;
files?: Hash<string>;
};
export function setupBuild(state: BuildState = {}): Build & {run: Run} {
const build = new Build();

build.apply();
build.hooks.BeforeAnyRun.tap('Tests', (run) => {
(build as Build & {run: Run}).run = run;

// @ts-ignore
run.glob = vi.fn(() => []);
run.copy = vi.fn();
run.write = vi.fn();
run.fs.writeFile = vi.fn();
// @ts-ignore
run.fs.readFile = vi.fn();
// @ts-ignore
run.logger.proc = vi.fn();
// @ts-ignore
run.logger.info = vi.fn();
// @ts-ignore
run.logger.warn = vi.fn();
// @ts-ignore
run.logger.error = vi.fn();

if (state.globs) {
for (const [pattern, files] of Object.entries(state.globs)) {
when(run.glob).calledWith(pattern, expect.anything()).thenResolve(files);
}
}

if (state.files) {
for (const [file, content] of Object.entries(state.files)) {
when(run.fs.readFile)
.calledWith(join(run.input, file), expect.anything())
.thenResolve(content);
}
}
});

return build as Build & {run: Run};
}

export async function runBuild(args: string, build?: Build) {
build = build || setupBuild();
await build.parse(['node', 'index'].concat(args.split(' ')));
}

Expand Down
105 changes: 105 additions & 0 deletions src/commands/build/core/vars/VarsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type {Preset, Presets} from './types';

import {dirname, join} from 'node:path';
import {merge} from 'lodash';
import {dump, load} from 'js-yaml';

import {Run} from '~/commands/build';
import {freeze, own} from '~/utils';
import {AsyncParallelHook, AsyncSeriesWaterfallHook} from 'tapable';

export type VarsServiceConfig = {
varsPreset: string;
vars: Hash;
};

type VarsServiceHooks = {
/**
* Async waterfall hook.
* Called after any presets.yaml was loaded.
*/
PresetsLoaded: AsyncSeriesWaterfallHook<[Presets, RelativePath]>;
/**
* Async parallel hook.
* Called after vars was resolved on any level.
* Vars data is sealed here.
*/
Resolved: AsyncParallelHook<[Preset, RelativePath]>;
};

export class VarsService {
hooks: VarsServiceHooks;

private run: Run;

private fs: Run['fs'];

private logger: Run['logger'];

private config: VarsServiceConfig;

private cache: Record<RelativePath, Hash> = {};

constructor(run: Run) {
this.run = run;
this.fs = run.fs;
this.logger = run.logger;
this.config = run.config;
this.hooks = {
PresetsLoaded: new AsyncSeriesWaterfallHook(['presets', 'path']),
Resolved: new AsyncParallelHook(['vars', 'path']),
};
}

async load(path: RelativePath) {
const varsPreset = this.config.varsPreset || 'default';
const file = join(dirname(path), 'presets.yaml');

if (this.cache[file]) {
return this.cache[file];
}

this.logger.proc(path);

const scopes = [];

if (dirname(path) !== '.') {
scopes.push(await this.load(dirname(path)));
}

try {
const presets = await this.hooks.PresetsLoaded.promise(
load(await this.fs.readFile(join(this.run.input, file), 'utf8')) as Presets,
file,
);

scopes.push(presets['default']);

if (varsPreset && varsPreset !== 'default') {
scopes.push(presets[varsPreset] || {});
}
} catch (error) {
if (!own(error, 'code') || error.code !== 'ENOENT') {
throw error;
}
}

scopes.push(this.config.vars);

this.cache[file] = freeze(merge({}, ...scopes));

await this.hooks.Resolved.promise(this.cache[file], file);

return this.cache[file];
}

dump(presets: Hash): string {
return dump(presets, {
lineWidth: 120,
});
}

entries() {
return Object.entries(this.cache);
}
}
57 changes: 57 additions & 0 deletions src/commands/build/core/vars/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`vars > service > load > should allow content extending in PresetsLoaded hook 1`] = `
"field1: value1
field2: value2
"
`;

exports[`vars > service > load > should allow content updating in PresetsLoaded hook 1`] = `
"field1: value2
"
`;

exports[`vars > service > load > should load presets file default scope 1`] = `
"field1: value1
field2: value2
"
`;

exports[`vars > service > load > should load presets file target scope 1`] = `
"field1: value3
field2: value2
"
`;

exports[`vars > service > load > should load super layers 1`] = `
"field1: value1
override1: value1
override2: value1
override3: value1
override4: value1
field2: value1
sub1: value1
sub2: value1
override5: value1
override6: value1
subsub1: value1
subsub2: value1
"
`;

exports[`vars > service > load > should override default presets with vars 1`] = `
"field1: value6
field2: value2
"
`;

exports[`vars > service > load > should override target presets with vars 1`] = `
"field1: value6
field2: value2
"
`;

exports[`vars > service > load > should use vars if presets not found 1`] = `
"field1: value6
"
`;
Loading

0 comments on commit ace3c5d

Please sign in to comment.