Skip to content

Commit 01deb31

Browse files
authored
feat: support create profile (#85)
* feat: support create profile * fix: regexp group index error * fix: cancel support discard profile changes
1 parent bc2e4f8 commit 01deb31

File tree

8 files changed

+282
-105
lines changed

8 files changed

+282
-105
lines changed

packages/scaffold/src/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ const defaultConfig = {
138138
devtools: true,
139139
startArgs: [],
140140
asProxy: false,
141+
prebuild: true,
142+
// keepProfileChanges: true,
143+
createProfileIfMissing: true,
141144
hooks: {},
142145
},
143146
addonLint: {},

packages/scaffold/src/core/server.ts

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export default class Serve extends Base {
1313
private runner?: ZoteroRunner;
1414

1515
private _zoteroBinPath?: string;
16-
private _profilePath?: string;
1716

1817
constructor(ctx: Context) {
1918
super(ctx);
@@ -27,23 +26,33 @@ export default class Serve extends Base {
2726
process.on("SIGINT", this.exit);
2827

2928
this.runner = new ZoteroRunner({
30-
binaryPath: this.zoteroBinPath,
31-
profilePath: this.profilePath,
32-
dataDir: this.dataDir,
33-
plugins: [{
34-
id: this.ctx.id,
35-
sourceDir: join(this.ctx.dist, "addon"),
36-
}],
37-
asProxy: this.ctx.server.asProxy,
38-
devtools: this.ctx.server.devtools,
39-
binaryArgs: this.ctx.server.startArgs,
29+
binary: {
30+
path: this.zoteroBinPath,
31+
devtools: this.ctx.server.devtools,
32+
args: this.ctx.server.startArgs,
33+
},
34+
profile: {
35+
path: this.profilePath,
36+
dataDir: this.dataDir,
37+
// keepChanges: this.ctx.server.keepProfileChanges,
38+
createIfMissing: this.ctx.server.createProfileIfMissing,
39+
},
40+
plugins: {
41+
list: [{
42+
id: this.ctx.id,
43+
sourceDir: join(this.ctx.dist, "addon"),
44+
}],
45+
asProxy: this.ctx.server.asProxy,
46+
},
4047
});
4148

4249
await this.ctx.hooks.callHook("serve:init", this.ctx);
4350

4451
// prebuild
45-
await this.builder.run();
46-
await this.ctx.hooks.callHook("serve:prebuild", this.ctx);
52+
if (this.ctx.server.prebuild) {
53+
await this.builder.run();
54+
await this.ctx.hooks.callHook("serve:prebuild", this.ctx);
55+
}
4756

4857
// start Zotero
4958
await this.runner.run();
@@ -133,17 +142,10 @@ export default class Serve extends Base {
133142
}
134143

135144
get profilePath() {
136-
if (this._profilePath)
137-
return this._profilePath;
138-
139-
this._profilePath = process.env.ZOTERO_PLUGIN_PROFILE_PATH;
140-
if (!this._profilePath || !existsSync(this._profilePath))
141-
throw new Error("The Zotero profile not found.");
142-
143-
return this._profilePath;
145+
return process.env.ZOTERO_PLUGIN_PROFILE_PATH;
144146
}
145147

146148
get dataDir() {
147-
return process.env.ZOTERO_PLUGIN_DATA_DIR ?? "";
149+
return process.env.ZOTERO_PLUGIN_DATA_DIR;
148150
}
149151
}

packages/scaffold/src/core/tester.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -561,19 +561,25 @@ export default class Test extends Base {
561561

562562
async startZotero() {
563563
this.runner = new ZoteroRunner({
564-
binaryPath: this.zoteroBinPath,
565-
profilePath: this.profilePath,
566-
dataDir: this.dataDir,
567-
plugins: [{
568-
id: this.ctx.id,
569-
sourceDir: join(this.ctx.dist, "addon"),
570-
}, {
571-
id: this.testPluginID,
572-
sourceDir: this.testPluginDir,
573-
}],
574-
devtools: this.ctx.server.devtools,
575-
binaryArgs: this.ctx.server.startArgs,
576-
customPrefs: this.prefs,
564+
binary: {
565+
path: this.zoteroBinPath,
566+
devtools: this.ctx.server.devtools,
567+
args: this.ctx.server.startArgs,
568+
},
569+
profile: {
570+
path: this.profilePath,
571+
dataDir: this.dataDir,
572+
customPrefs: this.prefs,
573+
},
574+
plugins: {
575+
list: [{
576+
id: this.ctx.id,
577+
sourceDir: join(this.ctx.dist, "addon"),
578+
}, {
579+
id: this.testPluginID,
580+
sourceDir: this.testPluginDir,
581+
}],
582+
},
577583
});
578584

579585
await this.runner.run();

packages/scaffold/src/types/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,11 @@ export interface ServerConfig {
347347
* @default false
348348
*/
349349
asProxy: boolean;
350+
351+
prebuild: boolean;
352+
// keepProfileChanges: boolean;
353+
createProfileIfMissing: boolean;
354+
350355
/**
351356
* The lifecycle hook.
352357
*/

packages/scaffold/src/types/index.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import type { Hookable } from "hookable";
22
import type { Log } from "../utils/log.js";
33
import type { Hooks } from "./config.js";
4+
import type { RecursivePartial } from "./utils.js";
45
import { Config } from "./config.js";
56

6-
type RecursivePartial<T> = {
7-
[P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
8-
};
7+
export { Config, Hooks };
98

109
/**
1110
* User config
1211
*/
13-
interface UserConfig extends RecursivePartial<Config> {}
12+
export interface UserConfig extends RecursivePartial<Config> {}
1413

15-
interface OverrideConfig extends RecursivePartial<Config> {}
14+
export interface OverrideConfig extends RecursivePartial<Config> {}
1615

17-
interface Context extends Config {
16+
export interface Context extends Config {
1817
pkgUser: any;
1918
version: string;
2019
hooks: Hookable<Hooks>;
@@ -34,5 +33,3 @@ export interface TemplateData {
3433
xpiName: string;
3534
buildTime: string;
3635
}
37-
38-
export { Config, Context, Hooks, OverrideConfig, UserConfig };
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export type RecursivePartial<T> = {
2+
[P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
3+
};
4+
5+
export type RecursiveRequired<T> = {
6+
// eslint-disable-next-line ts/no-unsafe-function-type
7+
[K in keyof T]-?: T[K] extends object ? T[K] extends Function ? T[K]
8+
: RecursiveRequired<T[K]>
9+
: T[K];
10+
};
11+
12+
export type RecursiveNonNullable<T> = {
13+
// eslint-disable-next-line ts/no-unsafe-function-type
14+
[K in keyof T]-?: T[K] extends object ? T[K] extends Function ? T[K]
15+
: RecursiveNonNullable<NonNullable<T[K]>>
16+
: NonNullable<T[K]>;
17+
};
18+
19+
type RecursiveOptionalKeys<T> = {
20+
[K in keyof T]-?: undefined extends T[K] ? K : T[K] extends object ? K | RecursiveOptionalKeys<T[K]> : never;
21+
}[keyof T];
22+
23+
export type RecursivePickOptional<T> = {
24+
[K in keyof T as K extends RecursiveOptionalKeys<T> ? K : never]?: T[K] extends object ? RecursivePickOptional<T[K]> : T[K];
25+
};
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { readFile } from "node:fs/promises";
2+
import { isNotNil } from "es-toolkit";
3+
import { outputFile } from "fs-extra/esm";
4+
import { logger } from "./log.js";
5+
import { prefs as defaultPrefs } from "./zotero/preference.js";
6+
7+
export type Prefs = Record<string, string | number | boolean | undefined | null>;
8+
9+
export class PrefsManager {
10+
private namespace: "pref" | "user_pref";
11+
private prefs: Prefs;
12+
13+
constructor(namespace: "pref" | "user_pref") {
14+
this.namespace = namespace;
15+
this.prefs = { ...defaultPrefs };
16+
}
17+
18+
private parsePrefjs(content: string) {
19+
// eslint-disable-next-line regexp/no-super-linear-backtracking
20+
const prefPattern = /^(pref|user_pref)\s*\(\s*["']([^"']+)["']\s*,\s*(.+)\s*\)\s*;$/gm;
21+
const matches = content.matchAll(prefPattern);
22+
for (const match of matches) {
23+
const key = match[2].trim();
24+
const value = match[3].trim();
25+
this.prefs[key] = value;
26+
};
27+
}
28+
29+
public async read(path: string) {
30+
const content = await readFile(path, "utf-8");
31+
this.parsePrefjs(content);
32+
}
33+
34+
private renderPrefjs() {
35+
return Object.entries(this.prefs).map(([key, value]) => {
36+
if (!isNotNil(value))
37+
return "";
38+
39+
let cleanValue = "";
40+
if (typeof value === "boolean") {
41+
cleanValue = `${value}`;
42+
}
43+
else if (typeof value === "string") {
44+
cleanValue = `${value.replace("\n", "\\n")}`;
45+
}
46+
else if (typeof value === "number") {
47+
cleanValue = value.toString();
48+
}
49+
50+
return `${this.namespace}("${key}", ${cleanValue});`;
51+
}).filter(c => !!c).join("\n");
52+
}
53+
54+
public async write(path: string) {
55+
const content = this.renderPrefjs();
56+
// console.log(content);
57+
await outputFile(path, content, "utf-8");
58+
logger.debug("The <profile>/prefs.js has been modified.");
59+
}
60+
61+
setPref(key: string, value: any) {
62+
this.prefs[key] = value;
63+
};
64+
65+
setPrefs(prefs: Prefs) {
66+
Object.entries(prefs).forEach(([key, value]) => {
67+
this.setPref(key, value);
68+
});
69+
}
70+
71+
getPref(key: string) {
72+
return this.prefs[key] ?? undefined;
73+
}
74+
75+
getPrefs() {
76+
return this.prefs;
77+
}
78+
}

0 commit comments

Comments
 (0)