@youcan/theme 2.0.5 → 2.1.0
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/dist/cli/commands/theme/dev.js +39 -5
- package/dist/cli/services/dev/execute.d.ts +4 -0
- package/dist/cli/services/dev/execute.js +28 -0
- package/dist/cli/services/dev/worker.d.ts +2 -2
- package/dist/cli/services/dev/worker.js +13 -62
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +11 -2
- package/package.json +2 -2
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { Session, Tasks, Http, Env } from '@youcan/cli-kit';
|
|
1
|
+
import { Session, Tasks, Http, Env, Path, Filesystem, Crypto } from '@youcan/cli-kit';
|
|
2
2
|
import { ThemeCommand } from '../../../util/theme-command.js';
|
|
3
3
|
import { load } from '../../../util/theme-loader.js';
|
|
4
4
|
import ThemeWorker from '../../services/dev/worker.js';
|
|
5
|
+
import { execute } from '../../services/dev/execute.js';
|
|
6
|
+
import { THEME_FILE_TYPES } from '../../../constants.js';
|
|
5
7
|
|
|
6
8
|
class Dev extends ThemeCommand {
|
|
7
9
|
static description = 'Start a theme development server and preview your changes';
|
|
@@ -10,13 +12,45 @@ class Dev extends ThemeCommand {
|
|
|
10
12
|
await Session.authenticate(this);
|
|
11
13
|
const context = await Tasks.run({ cmd: this, workers: [] }, [
|
|
12
14
|
{
|
|
13
|
-
title: 'Fetching theme
|
|
15
|
+
title: 'Fetching theme metadata...',
|
|
14
16
|
async task(ctx) {
|
|
15
|
-
const
|
|
17
|
+
const store = await Http.get(`${Env.apiHostname()}/me`);
|
|
16
18
|
ctx.store = {
|
|
17
|
-
slug:
|
|
18
|
-
domain:
|
|
19
|
+
slug: store.slug,
|
|
20
|
+
domain: store.domain,
|
|
19
21
|
};
|
|
22
|
+
const metadata = await Http.get(`${Env.apiHostname()}/themes/${theme.theme_id}/metadata`);
|
|
23
|
+
theme.metadata = metadata;
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
title: 'Syncing theme files, please wait...',
|
|
28
|
+
async task(ctx) {
|
|
29
|
+
for (const type of THEME_FILE_TYPES) {
|
|
30
|
+
const descriptors = theme.metadata[type] ?? [];
|
|
31
|
+
const directory = Path.resolve(theme.root, type);
|
|
32
|
+
const present = await Filesystem.exists(directory)
|
|
33
|
+
? await Filesystem.readdir(directory)
|
|
34
|
+
: [];
|
|
35
|
+
if (type === 'config') {
|
|
36
|
+
const order = ['settings_schema.json', 'settings_data.json'];
|
|
37
|
+
descriptors.sort((a, b) => order.indexOf(a.file_name) - order.indexOf(b.file_name));
|
|
38
|
+
}
|
|
39
|
+
for (const file of present.filter(f => !descriptors.find(d => d.file_name === f))) {
|
|
40
|
+
await execute(theme, 'save', type, file);
|
|
41
|
+
}
|
|
42
|
+
for (const descriptor of descriptors) {
|
|
43
|
+
const path = Path.resolve(directory, descriptor.file_name);
|
|
44
|
+
if (!(await Filesystem.exists(path))) {
|
|
45
|
+
return await execute(theme, 'delete', type, descriptor.file_name);
|
|
46
|
+
}
|
|
47
|
+
const buffer = await Filesystem.readFile(path);
|
|
48
|
+
const hash = Crypto.sha1(buffer);
|
|
49
|
+
if (hash !== descriptor.hash) {
|
|
50
|
+
await execute(theme, 'save', type, descriptor.file_name);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
20
54
|
},
|
|
21
55
|
},
|
|
22
56
|
{
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Worker } from '@youcan/cli-kit';
|
|
2
|
+
import type { Theme } from '@/types';
|
|
3
|
+
import type { THEME_FILE_TYPES } from '@/constants';
|
|
4
|
+
export declare function execute(theme: Theme, op: 'save' | 'delete', type: typeof THEME_FILE_TYPES[number], name: string, logger?: Worker.Logger | null): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Path, Form, Http, Env } from '@youcan/cli-kit';
|
|
2
|
+
|
|
3
|
+
async function execute(theme, op, type, name, logger = null) {
|
|
4
|
+
try {
|
|
5
|
+
const path = Path.join(theme.root, type, name);
|
|
6
|
+
const payload = {
|
|
7
|
+
file_name: name,
|
|
8
|
+
file_type: type,
|
|
9
|
+
file_operation: op,
|
|
10
|
+
};
|
|
11
|
+
if (op === 'save') {
|
|
12
|
+
payload.file_content = await Form.file(path);
|
|
13
|
+
}
|
|
14
|
+
await Http.post(`${Env.apiHostname()}/themes/${theme.theme_id}/update`, {
|
|
15
|
+
body: Form.convert(payload),
|
|
16
|
+
});
|
|
17
|
+
logger && logger.write(`[${op === 'save' ? 'updated' : 'deleted'}] - ${Path.join(type, name)}`);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err instanceof Error && logger) {
|
|
21
|
+
logger.write(`[error] - ${Path.join(type, name)}\n${err.message}`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { execute };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Worker } from '@youcan/cli-kit';
|
|
2
2
|
import type { ThemeCommand } from '@/util/theme-command';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Store, Theme } from '@/types';
|
|
4
4
|
export default class ThemeWorker extends Worker.Abstract {
|
|
5
5
|
private command;
|
|
6
6
|
private store;
|
|
@@ -9,9 +9,9 @@ export default class ThemeWorker extends Worker.Abstract {
|
|
|
9
9
|
private previewLogger;
|
|
10
10
|
private queue;
|
|
11
11
|
private io;
|
|
12
|
-
FILE_TYPES: Array<keyof Metadata>;
|
|
13
12
|
constructor(command: ThemeCommand, store: Store, theme: Theme);
|
|
14
13
|
boot(): Promise<void>;
|
|
15
14
|
run(): Promise<void>;
|
|
16
15
|
private enqueue;
|
|
16
|
+
private execute;
|
|
17
17
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { Worker, Color, Http,
|
|
1
|
+
import { Worker, Color, Http, System, Path, Filesystem } from '@youcan/cli-kit';
|
|
2
2
|
import { Server } from 'socket.io';
|
|
3
3
|
import debounce from 'debounce';
|
|
4
|
+
import { execute } from './execute.js';
|
|
5
|
+
import { THEME_FILE_TYPES } from '../../../constants.js';
|
|
4
6
|
|
|
5
7
|
class ThemeWorker extends Worker.Abstract {
|
|
6
8
|
command;
|
|
@@ -10,15 +12,6 @@ class ThemeWorker extends Worker.Abstract {
|
|
|
10
12
|
previewLogger;
|
|
11
13
|
queue = [];
|
|
12
14
|
io;
|
|
13
|
-
FILE_TYPES = [
|
|
14
|
-
'layouts',
|
|
15
|
-
'sections',
|
|
16
|
-
'locales',
|
|
17
|
-
'assets',
|
|
18
|
-
'snippets',
|
|
19
|
-
'config',
|
|
20
|
-
'templates',
|
|
21
|
-
];
|
|
22
15
|
constructor(command, store, theme) {
|
|
23
16
|
super();
|
|
24
17
|
this.command = command;
|
|
@@ -29,33 +22,6 @@ class ThemeWorker extends Worker.Abstract {
|
|
|
29
22
|
}
|
|
30
23
|
async boot() {
|
|
31
24
|
try {
|
|
32
|
-
const res = await Http.get(`${Env.apiHostname()}/themes/${this.theme.theme_id}/metadata`);
|
|
33
|
-
this.theme.metadata = res;
|
|
34
|
-
this.logger.write(`pushing changes to ${this.theme.metadata.theme_name}...`);
|
|
35
|
-
for (const type of this.FILE_TYPES) {
|
|
36
|
-
const descriptors = this.theme.metadata[type] ?? [];
|
|
37
|
-
const directory = Path.resolve(this.theme.root, type);
|
|
38
|
-
const present = await Filesystem.exists(directory)
|
|
39
|
-
? await Filesystem.readdir(directory)
|
|
40
|
-
: [];
|
|
41
|
-
if (type === 'config') {
|
|
42
|
-
const order = ['settings_schema.json', 'settings_data.json'];
|
|
43
|
-
descriptors.sort((a, b) => order.indexOf(a.file_name) - order.indexOf(b.file_name));
|
|
44
|
-
}
|
|
45
|
-
present.filter(f => !descriptors.find(d => d.file_name === f))
|
|
46
|
-
.forEach(async (file) => this.enqueue('save', type, file));
|
|
47
|
-
descriptors.forEach(async (descriptor) => {
|
|
48
|
-
const path = Path.resolve(directory, descriptor.file_name);
|
|
49
|
-
if (!(await Filesystem.exists(path))) {
|
|
50
|
-
return this.enqueue('delete', type, descriptor.file_name);
|
|
51
|
-
}
|
|
52
|
-
const buffer = await Filesystem.readFile(path);
|
|
53
|
-
const hash = Crypto.sha1(buffer);
|
|
54
|
-
if (hash !== descriptor.hash) {
|
|
55
|
-
this.enqueue('save', type, descriptor.file_name);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
25
|
this.io = new Server(7565, {
|
|
60
26
|
cors: {
|
|
61
27
|
origin: `${Http.scheme()}://${this.store.domain}`,
|
|
@@ -72,7 +38,7 @@ class ThemeWorker extends Worker.Abstract {
|
|
|
72
38
|
}
|
|
73
39
|
}
|
|
74
40
|
async run() {
|
|
75
|
-
const directories =
|
|
41
|
+
const directories = THEME_FILE_TYPES.map(t => Path.resolve(this.theme.root, t));
|
|
76
42
|
const watcher = Filesystem.watch(directories, {
|
|
77
43
|
awaitWriteFinish: { stabilityThreshold: 50 },
|
|
78
44
|
ignoreInitial: true,
|
|
@@ -99,6 +65,7 @@ class ThemeWorker extends Worker.Abstract {
|
|
|
99
65
|
break;
|
|
100
66
|
}
|
|
101
67
|
});
|
|
68
|
+
this.logger.write('Listening for changes...');
|
|
102
69
|
// quick racing conditions hack
|
|
103
70
|
setInterval(async () => {
|
|
104
71
|
const task = this.queue.shift();
|
|
@@ -110,32 +77,16 @@ class ThemeWorker extends Worker.Abstract {
|
|
|
110
77
|
}
|
|
111
78
|
enqueue(op, type, name) {
|
|
112
79
|
this.queue.push(async () => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
file_operation: op,
|
|
119
|
-
};
|
|
120
|
-
if (op === 'save') {
|
|
121
|
-
payload.file_content = await Form.file(path);
|
|
122
|
-
}
|
|
123
|
-
await Http.post(`${Env.apiHostname()}/themes/${this.theme.theme_id}/update`, {
|
|
124
|
-
body: Form.convert(payload),
|
|
125
|
-
});
|
|
126
|
-
this.logger.write(`[${op === 'save' ? 'updated' : 'deleted'}] - ${Path.join(type, name)}`);
|
|
127
|
-
debounce(() => {
|
|
128
|
-
this.io.emit('theme:update');
|
|
129
|
-
this.previewLogger.write('reloading preview...');
|
|
130
|
-
}, 100)();
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
if (err instanceof Error) {
|
|
134
|
-
this.logger.write(`[error] - ${Path.join(type, name)}\n${err.message}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
80
|
+
await this.execute(op, type, name, true);
|
|
81
|
+
debounce(() => {
|
|
82
|
+
this.io.emit('theme:update');
|
|
83
|
+
this.previewLogger.write('reloading preview...');
|
|
84
|
+
}, 100)();
|
|
137
85
|
});
|
|
138
86
|
}
|
|
87
|
+
async execute(op, type, name, log = false) {
|
|
88
|
+
return execute(this.theme, op, type, name, this.logger);
|
|
89
|
+
}
|
|
139
90
|
}
|
|
140
91
|
|
|
141
92
|
export { ThemeWorker as default };
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
const THEME_CONFIG_FILENAME = 'youcan.theme.json';
|
|
1
|
+
const THEME_CONFIG_FILENAME = 'youcan.theme.json';
|
|
2
|
+
const THEME_FILE_TYPES = [
|
|
3
|
+
'layouts',
|
|
4
|
+
'sections',
|
|
5
|
+
'locales',
|
|
6
|
+
'assets',
|
|
7
|
+
'snippets',
|
|
8
|
+
'config',
|
|
9
|
+
'templates',
|
|
10
|
+
];
|
|
2
11
|
|
|
3
|
-
export { THEME_CONFIG_FILENAME };
|
|
12
|
+
export { THEME_CONFIG_FILENAME, THEME_FILE_TYPES };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youcan/theme",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"description": "OCLIF plugin for building themes",
|
|
6
6
|
"author": "YouCan <contact@youcan.shop> (https://youcan.shop)",
|
|
7
7
|
"license": "MIT",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@oclif/core": "^2.15.0",
|
|
19
19
|
"debounce": "^2.0.0",
|
|
20
20
|
"socket.io": "^4.7.2",
|
|
21
|
-
"@youcan/cli-kit": "2.0
|
|
21
|
+
"@youcan/cli-kit": "2.1.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@oclif/plugin-legacy": "^1.3.0",
|