Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
798afce
refactor(model): add Code model for include_code tag
D-Sketon Feb 13, 2025
caf9d9c
add test
D-Sketon Feb 15, 2025
ae5fae7
fix(page): exclude code directory from processing
D-Sketon Feb 15, 2025
c03d4ce
Merge branch 'master' into fix/code
D-Sketon Mar 5, 2025
98cf8c5
Merge branch 'master' into fix/code
D-Sketon Mar 8, 2025
b1d66ef
Merge branch 'master' into fix/code
D-Sketon Apr 21, 2025
0f5713d
Merge branch 'master' into fix/code
D-Sketon Apr 22, 2025
d1bbd80
Merge branch 'master' into fix/code
D-Sketon May 5, 2025
80ec349
Merge branch 'master' into fix/code
D-Sketon Jul 24, 2025
3e4bd21
Merge branch 'master' into fix/code
D-Sketon Sep 15, 2025
724853f
Merge branch 'master' into fix/code
D-Sketon Oct 24, 2025
8e54ce4
Merge branch 'master' into fix/code
D-Sketon Oct 25, 2025
76a3c61
Merge branch 'master' into fix/code
D-Sketon Aug 8, 2026
98d3bb9
fix(processor/code): enhance file exclusion logic in pattern matching
D-Sketon Aug 9, 2026
7b6dd05
fix(tests): update code removal to use await for asynchronous operations
D-Sketon Aug 9, 2026
0381f4c
Merge branch 'fix/code' of https://github.com/D-Sketon/hexo into fix/…
D-Sketon Aug 9, 2026
dc0b841
fix(processor/code): update slug assignment to use file path for bett…
D-Sketon Aug 9, 2026
4c0af71
Merge branch 'master' into fix/code
D-Sketon Aug 9, 2026
bc05d0f
fix(processor): refactor code_dir handling for consistency in asset a…
D-Sketon Aug 15, 2026
0687880
Merge branch 'fix/code' of https://github.com/D-Sketon/hexo into fix/…
D-Sketon Aug 15, 2026
e17b32e
fix(processor): ensure document modification status is updated correc…
D-Sketon Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/models/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import warehouse from 'warehouse';
import type Hexo from '../hexo';
import { CodeSchema } from '../types';
import { join } from 'path';

export = (ctx: Hexo) => {
const Code = new warehouse.Schema<CodeSchema>({
_id: { type: String, required: true },
path: { type: String, required: true },
slug: { type: String, required: true },
modified: { type: Boolean, default: true },
content: { type: String, default: '' }
});

Code.virtual('source').get(function() {
return join(ctx.base_dir, this._id);
});

return Code;
};
1 change: 1 addition & 0 deletions lib/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { default as PostAsset } from './post_asset';
export { default as PostCategory } from './post_category';
export { default as PostTag } from './post_tag';
export { default as Tag } from './tag';
export { default as Code } from './code';
6 changes: 3 additions & 3 deletions lib/plugins/generator/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Promise from 'bluebird';
import { extname } from 'path';
import { magenta } from 'picocolors';
import type Hexo from '../../hexo';
import type { AssetSchema, BaseGeneratorReturn } from '../../types';
import type { AssetSchema, BaseGeneratorReturn, PostAssetSchema } from '../../types';
import type Document from 'warehouse/dist/document';

interface AssetData {
Expand All @@ -19,9 +19,9 @@ interface AssetGenerator extends BaseGeneratorReturn {
}

const process = (name: string, ctx: Hexo) => {
return Promise.filter(ctx.model(name).toArray(), (asset: Document<AssetSchema>) => exists(asset.source).tap(exist => {
return Promise.filter(ctx.model(name).toArray(), (asset: Document<AssetSchema> | Document<PostAssetSchema>) => exists(asset.source).tap(exist => {
if (!exist) return asset.remove();
})).map((asset: Document<AssetSchema>) => {
})).map((asset: Document<AssetSchema> | Document<PostAssetSchema>) => {
const { source } = asset;
let { path } = asset;
const data: AssetData = {
Expand Down
27 changes: 27 additions & 0 deletions lib/plugins/generator/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type Hexo from '../../hexo';
import Promise from 'bluebird';
import { exists } from 'hexo-fs';
import type { CodeSchema } from '../../types';
import type Document from 'warehouse/dist/document';

interface CodeData {
modified: boolean;
data: string;
}

function codeGenerator(this: Hexo): Promise<any[]> {
return Promise.filter(this.model('Code').toArray(), (code: Document<CodeSchema>) => exists(code.source).tap(exist => {
if (!exist) return code.remove();
})).map((code: Document<CodeSchema>) => {
const { path } = code;
const data: CodeData = {
modified: code.modified,
data: code.content
};

return { path, data };
});

}

export = codeGenerator;
1 change: 1 addition & 0 deletions lib/plugins/generator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type Hexo from '../../hexo';
export = (ctx: Hexo) => {
const { generator } = ctx.extend;

generator.register('code', require('./code'));
generator.register('asset', require('./asset'));
generator.register('page', require('./page'));
generator.register('post', require('./post'));
Expand Down
3 changes: 3 additions & 0 deletions lib/plugins/processor/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export = (ctx: Hexo) => {
return {
pattern: new Pattern(path => {
if (isExcludedFile(path, ctx.config)) return;
let codeDir = ctx.config.code_dir;
if (!codeDir.endsWith('/')) codeDir += '/';
if (path.startsWith(codeDir)) return;

return {
renderable: ctx.render.isRenderable(path) && !isMatch(path, ctx.config.skip_render)
Expand Down
46 changes: 46 additions & 0 deletions lib/plugins/processor/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Pattern } from 'hexo-util';
import { relative } from 'path';
import { isExcludedFile } from './common';
import type Hexo from '../../hexo';
import type { _File } from '../../box';

export = (ctx: Hexo) => {
return {
pattern: new Pattern(path => {
let codeDir = ctx.config.code_dir;
if (!codeDir.endsWith('/')) codeDir += '/';
if (!path.startsWith(codeDir)) return false;
if (isExcludedFile(path, ctx.config)) return false;
return true;
}),
process: function codeProcessor(file: _File) {
const id = relative(ctx.base_dir, file.source).replace(/\\/g, '/');
const slug = file.path;
const Code = ctx.model('Code');
const doc = Code.findById(id);

if (file.type === 'delete') {
if (doc) {
return doc.remove();
}

return;
}

if (file.type === 'skip' && doc) {
Comment thread
D-Sketon marked this conversation as resolved.
doc.modified = false;
return doc.save();
}

return file.read().then(content => {
return Code.save({
_id: id,
path: file.path,
slug,
modified: file.type !== 'skip',
content
});
});
}
};
};
1 change: 1 addition & 0 deletions lib/plugins/processor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export = (ctx: Hexo) => {
processor.register(obj.pattern, obj.process);
}

register('code');
register('asset');
register('data');
register('post');
Expand Down
4 changes: 2 additions & 2 deletions lib/plugins/tag/include_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export = (ctx: Hexo) => function includeCodeTag(args: string[]) {
const source = join(codeDir, path).replace(/\\/g, '/');

// Prevent path traversal: https://github.com/hexojs/hexo/issues/5250
const Page = ctx.model('Page');
const doc = Page.findOne({ source });
const Code = ctx.model('Code');
const doc = Code.findOne({ slug: source });
if (!doc) return;

let code = doc.content;
Expand Down
9 changes: 9 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,15 @@ export interface PageSchema extends BasePagePostSchema {
tag?: string;
}

export interface CodeSchema {
_id: string;
path: string;
slug: string;
modified: boolean;
content: string;
source: string;
}

export interface AssetSchema {
_id?: string;
path: string;
Expand Down
116 changes: 116 additions & 0 deletions test/scripts/generators/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { join } from 'path';
import { mkdirs, rmdir, unlink, writeFile } from 'hexo-fs';
import Hexo from '../../../lib/hexo';
import codeGenerator from '../../../lib/plugins/generator/code';
import defaults from '../../../lib/hexo/default_config';
import chai from 'chai';
const should = chai.should();
type CodeParams = Parameters<typeof codeGenerator>
type CodeReturn = ReturnType<typeof codeGenerator>

describe('code', () => {
const hexo = new Hexo(join(__dirname, 'code_test'), {silent: true});
const generator: (...args: CodeParams) => CodeReturn = codeGenerator.bind(hexo);
const Code = hexo.model('Code');
const codeDir = defaults.code_dir;

before(async () => {
await mkdirs(hexo.base_dir);
await hexo.init();
});

after(() => rmdir(hexo.base_dir));

it('renderable', async () => {
const path = 'test.j2';
const source = join(hexo.base_dir, defaults.source_dir, defaults.code_dir, path);
const content = '{{ 1 }}';

await Promise.all([
Code.insert({
_id: `${defaults.source_dir}/${codeDir}/${path}`,
slug: `${codeDir}/${path}`,
path: `${codeDir}/${path}`,
content
}),
writeFile(source, content)
]);
const data = await generator();
const item = data.find(d => d.path === `${codeDir}/${path}`);
should.exist(item);
item.path.should.eql(`${codeDir}/${path}`);
item.data.modified.should.be.true;

const result = await item.data.data;
result.should.eql(content);

await Promise.all([
Code.removeById(`${defaults.source_dir}/${codeDir}/${path}`),
unlink(source)
]);
});

it('not renderable', async () => {
const path = 'test.txt';
const source = join(hexo.base_dir, defaults.source_dir, defaults.code_dir, path);
const content = 'test content';

await Promise.all([
Code.insert({
_id: `${defaults.source_dir}/${codeDir}/${path}`,
slug: `${codeDir}/${path}`,
path: `${codeDir}/${path}`,
content
}),
writeFile(source, content)
]);
const data = await generator();
const item = data.find(d => d.path === `${codeDir}/${path}`);
should.exist(item);
item.path.should.eql(`${codeDir}/${path}`);
item.data.modified.should.be.true;

const result = await item.data.data;
result.should.eql(content);

await Promise.all([
Code.removeById(`${defaults.source_dir}/${codeDir}/${path}`),
unlink(source)
]);
});

it('remove codes which does not exist', async () => {
const path = 'test.js';

await Code.insert({
_id: `${defaults.source_dir}/${codeDir}/${path}`,
slug: `${codeDir}/${path}`,
path: `${codeDir}/${path}`
});
await generator();
should.not.exist(Code.findById(`${defaults.source_dir}/${codeDir}/${path}`));
});

it('don\'t remove extension name', async () => {
const path = 'test.min.js';
const source = join(hexo.base_dir, defaults.source_dir, defaults.code_dir, path);

await Promise.all([
Code.insert({
_id: `${defaults.source_dir}/${codeDir}/${path}`,
slug: `${codeDir}/${path}`,
path: `${codeDir}/${path}`
}),
writeFile(source, '')
]);
const data = await generator();
const item = data.find(d => d.path === `${codeDir}/${path}`);
should.exist(item);
item.path.should.eql(`${codeDir}/${path}`);

await Promise.all([
Code.removeById(`${defaults.source_dir}/${codeDir}/${path}`),
unlink(source)
]);
});
});
59 changes: 59 additions & 0 deletions test/scripts/models/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { join } from 'path';
import Hexo from '../../../lib/hexo';

describe('Code', () => {
const hexo = new Hexo();
const Code = hexo.model('Code');

it('_id - required', async () => {
try {
await Code.insert({});
} catch (err) {
err.message.should.eql('ID is not defined');
}
});

it('path - required', async () => {
try {
await Code.insert({
_id: 'foo'
});
} catch (err) {
err.message.should.eql('`path` is required!');
}
});

it('slug - required', async () => {
try {
await Code.insert({
_id: 'foo',
path: 'bar'
});
} catch (err) {
err.message.should.eql('`slug` is required!');
}
});

it('default values', async () => {
const data = await Code.insert({
_id: 'foo',
path: 'bar',
slug: 'baz'
});
data.modified.should.be.true;
data.content.should.eql('');

Code.removeById(data._id);
});

it('source - virtual', async () => {
const data = await Code.insert({
_id: 'foo',
path: 'bar',
slug: 'baz'
});
data.source.should.eql(join(hexo.base_dir, data._id));

Code.removeById(data._id);
});
});
5 changes: 5 additions & 0 deletions test/scripts/processors/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ describe('asset', () => {
hexo.config.skip_render = ['fff/**'];
pattern.match('fff/foo.json').should.have.property('renderable', false);
hexo.config.skip_render = [];

// Custom code_dir
hexo.config.code_dir = 'snippets';
should.not.exist(pattern.match('snippets/foo.json'));
should.exist(pattern.match('foo.json'));
});

it('asset - type: create', async () => {
Expand Down
Loading
Loading