@peachy/plugin-resources 0.0.10 → 0.0.12

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/README.md CHANGED
@@ -2,11 +2,19 @@
2
2
 
3
3
  Allow you to import files as resources.
4
4
 
5
+ > NOTE: In dev mode, your resources will NOT be watched for changes and live-reloaded.
6
+
5
7
  ## Usage
6
8
 
7
- You will need to have peachy installed.
9
+ You will need to have `peachy` installed.
10
+
11
+ ### Configuration
12
+
13
+ Then you will need to create a peachy configuration file. There are two ways to do this:
8
14
 
9
- Then, you need to create a `peachy.config.ts` file in the root of your project.
15
+ #### 1. Use `peachy.config.ts`
16
+
17
+ You can create a `peachy.config.ts` file in the root of your project.
10
18
 
11
19
  ```ts
12
20
  // peachy.config.ts
@@ -14,16 +22,30 @@ Then, you need to create a `peachy.config.ts` file in the root of your project.
14
22
  import { defineConfig } from "@peachy/core";
15
23
 
16
24
  export default defineConfig({
17
- package: {
18
- name: "dev.peachy.Example"
19
- },
25
+ applicationId: "dev.peachy.Example",
20
26
  resources: {
21
27
  icons: true,
22
- }
28
+ },
23
29
  });
24
30
  ```
25
31
 
26
- TODO: enable this by default, skip generation if there's no resources/icons
32
+ #### 2. Use a field in `package.json`
33
+
34
+ You can also add a special `peachy` field in your existing package.json
35
+
36
+ ```json
37
+ // package.json
38
+ {
39
+ "peachy": {
40
+ "applicationId": "dev.peachy.Example",
41
+ "resources": {
42
+ "icons": true
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### Initialization
27
49
 
28
50
  In your code, make sure to initialize your code to use the given name in your `peachy.config.ts` file.
29
51
 
@@ -31,7 +53,7 @@ In your code, make sure to initialize your code to use the given name in your `p
31
53
  import Gtk from "gi://Gtk?version=4.0";
32
54
 
33
55
  const app = new Gtk.Application({
34
- // IMPORTANT: match this with the one in `peachy.config.ts`
56
+ // IMPORTANT: match this with the one in the configuration file
35
57
  application_id: "dev.peachy.Example",
36
58
  flags: Gtk.ApplicationFlags.FLAGS_NONE,
37
59
  });
@@ -43,7 +65,6 @@ app.run([]);
43
65
 
44
66
  Then you can start importing resources/icons.
45
67
 
46
-
47
68
  ### Importing Files
48
69
 
49
70
  When you need to import a resource, you can directly import them. Currently, only SVG files are supported.
@@ -64,6 +85,22 @@ You can use the imported resource anywhere that accepts a `Gio.Resource` like `G
64
85
 
65
86
  Icons in `data/icons` folder will be automatically configured and registered as Themed Icons, so you can use them just by referencing their name.
66
87
 
88
+ If your icons are in a different folder, you can specify the path in the configuration file.
89
+
90
+ ```ts
91
+ // peachy.config.ts
92
+
93
+ import { defineConfig } from "@peachy/core";
94
+
95
+ export default defineConfig({
96
+ applicationId: "dev.peachy.Example",
97
+ resources: {
98
+ // specify the path to your icons folder
99
+ icons: "./path/to/icons",
100
+ },
101
+ });
102
+ ```
103
+
67
104
  #### 1. Put your icons in `data/icons` folder.
68
105
 
69
106
  ```
@@ -80,3 +117,34 @@ const button = new Gtk.Button({
80
117
  icon_name: "right-symbolic",
81
118
  });
82
119
  ```
120
+
121
+ ### Bundle resources
122
+
123
+ If you have resources that you want to always be bundled in your application, you can "bundle" them.
124
+
125
+ These files can be read by objects that uses "Automatic resources" such as
126
+ [Gtk.Application](https://gnome.pages.gitlab.gnome.org/gtk/gtk4/class.Application.html#automatic-resources) and
127
+ [Adw.Application](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Application.html).
128
+
129
+ A few use cases are given below:
130
+
131
+ - The `gtk/help-overlay.ui` file will be used to display application help.
132
+ - The `style.css` file will be automatically loaded and applied to your application.
133
+
134
+ ```ts
135
+ // peachy.config.ts
136
+
137
+ import { defineConfig } from "@peachy/core";
138
+
139
+ export default defineConfig({
140
+ applicationId: "dev.peachy.Example",
141
+ resources: {
142
+ bundle: [
143
+ // this assumes that the file is located at `gtk/help-overlay.ui`
144
+ "gtk/help-overlay.ui",
145
+ // if you have a file located elsewhere, you can specify the path and alias it will be available at
146
+ { path: "./path/to/style.css", alias: "style.css" },
147
+ ],
148
+ },
149
+ });
150
+ ```
package/dist/index.d.mts CHANGED
@@ -2,12 +2,17 @@ import { Plugin } from "rolldown";
2
2
 
3
3
  //#region src/types.d.ts
4
4
  interface ResourcesPluginOptions {
5
- setRunnerEnv(key: string, value: string): void;
5
+ setRunnerEnv(this: void, key: string, value: string): void;
6
6
  applicationId?: string;
7
7
  prod: boolean;
8
8
  outdir: string;
9
9
  iconsPath?: string | false;
10
+ bundle?: BundledResource[];
10
11
  }
12
+ type BundledResource = string | {
13
+ alias?: string;
14
+ path: string;
15
+ };
11
16
  //#endregion
12
17
  //#region src/plugins/index.d.ts
13
18
  declare function resourcesPlugin(config: ResourcesPluginOptions): Plugin[];
package/dist/index.mjs CHANGED
@@ -1,17 +1,15 @@
1
1
  import { basename, dirname, join, relative, resolve } from "node:path";
2
- import path from "path";
3
2
  import fs from "fs/promises";
4
- import { access, mkdir, mkdtempDisposable, readFile, readdir, stat, writeFile } from "node:fs/promises";
3
+ import path from "path";
5
4
  import { exactRegex, prefixRegex } from "rolldown/filter";
6
- import { tmpdir } from "node:os";
5
+ import { access, mkdir, mkdtempDisposable, readFile, readdir, stat, writeFile } from "node:fs/promises";
7
6
  import { exec } from "node:child_process";
7
+ import { tmpdir } from "node:os";
8
8
  import { promisify } from "node:util";
9
-
10
9
  //#region src/constants.ts
11
10
  const GRESOURCE_PREFIX = "\0gresource";
12
11
  const GRESOURCE_LOADER = `${GRESOURCE_PREFIX}:loader`;
13
12
  const GRESOURCE_ICONS_PATH = "icons/scalable/actions";
14
-
15
13
  //#endregion
16
14
  //#region src/utilities/index.ts
17
15
  function parseImportType(assertions) {
@@ -56,34 +54,33 @@ function generateImportCode(type, resourcePath, addImportCode = false) {
56
54
  if (addImportCode) return `import ${JSON.stringify(GRESOURCE_LOADER)};${code}`;
57
55
  return code;
58
56
  }
59
- async function loadIcons(prefix, path$1) {
60
- if (!access(path$1).catch(() => false)) throw new Error(`Icon assets directory not found: ${path$1}`);
61
- const files = await readdir(path$1, { recursive: true });
57
+ async function loadIcons(prefix, path) {
58
+ if (!access(path).catch(() => false)) throw new Error(`Icon assets directory not found: ${path}`);
59
+ const files = await readdir(path, { recursive: true });
62
60
  const collectedAssets = /* @__PURE__ */ new Map();
63
61
  for (const file of files) {
64
- const fullName = resolve(path$1, file);
62
+ const fullName = resolve(path, file);
65
63
  const stats = await stat(fullName);
66
64
  if (!fullName.endsWith(".svg") || !stats.isFile()) continue;
67
65
  const id = relative(process.cwd(), fullName);
68
66
  collectedAssets.set(fullName, {
69
67
  internalResourceId: id,
70
- resourceId: getResourceId(prefix, id),
71
- path: fullName
68
+ resourceId: getResourceId(prefix, id)
72
69
  });
73
70
  }
74
71
  return collectedAssets;
75
72
  }
76
- function getFilePath(path$1, alias) {
73
+ function getFilePath(path, alias) {
77
74
  let attrs = "";
78
- if (/\.(svg|xml)$/.test(path$1)) attrs = " compressed=\"true\" preprocess=\"xml-stripblanks\"";
79
- else if (/\.(png|jpg|jpeg)$/.test(path$1)) attrs = " compressed=\"true\"";
80
- else if (/\.(json)$/.test(path$1)) attrs = " preprocess=\"json-stripblanks\"";
75
+ if (/\.(svg|xml)$/.test(path)) attrs = " compressed=\"true\" preprocess=\"xml-stripblanks\"";
76
+ else if (/\.(png|jpg|jpeg)$/.test(path)) attrs = " compressed=\"true\"";
77
+ else if (/\.(json)$/.test(path)) attrs = " preprocess=\"json-stripblanks\"";
81
78
  if (alias) attrs += ` alias="${alias}"`;
82
- return ` <file${attrs}>${path$1}</file>`;
79
+ return ` <file${attrs}>${path}</file>`;
83
80
  }
84
81
  function generateGResourceXML(prefix, assets, icons) {
85
- const files = [...assets.values()].map(({ internalResourceId }) => getFilePath(internalResourceId)).join("\n");
86
- const iconFiles = icons ? [...icons.values()].map(({ internalResourceId }) => getFilePath(internalResourceId, basename(internalResourceId))).join("\n") : "";
82
+ const files = Array.from(assets).map(([id, { internalResourceId }]) => getFilePath(id, internalResourceId)).join("\n");
83
+ const iconFiles = icons ? Array.from(icons).map(([id, { internalResourceId }]) => getFilePath(id, basename(internalResourceId))).join("\n") : "";
87
84
  return `<?xml version="1.0" encoding="UTF-8"?>
88
85
  <gresources>
89
86
  <gresource prefix="${prefix}">
@@ -94,7 +91,20 @@ ${iconFiles}
94
91
  </gresource>` : ""}
95
92
  </gresources>`;
96
93
  }
97
-
94
+ //#endregion
95
+ //#region src/utilities/bundle.ts
96
+ function addBundledResources(collectedResources, prefix, bundle = []) {
97
+ for (const bundledResource of bundle) {
98
+ const resourcePath = typeof bundledResource == "string" ? bundledResource : bundledResource.path;
99
+ const resourceAlias = typeof bundledResource == "object" ? bundledResource.alias : null;
100
+ const id = path.resolve(process.cwd(), resourcePath);
101
+ const internalResourceId = resourceAlias ?? resourcePath;
102
+ collectedResources.set(id, {
103
+ internalResourceId,
104
+ resourceId: getResourceId(prefix, internalResourceId)
105
+ });
106
+ }
107
+ }
98
108
  //#endregion
99
109
  //#region src/utilities/icons.ts
100
110
  async function getIconsPath(iconsPath) {
@@ -108,13 +118,12 @@ async function collectIcons(baseIconsPath, prefix) {
108
118
  if (iconsPath) collectedIcons = await loadIcons(prefix, iconsPath);
109
119
  return collectedIcons;
110
120
  }
111
-
112
121
  //#endregion
113
122
  //#region src/plugins/dev/index.ts
114
123
  /**
115
124
  * This plugin configures a GRESOURCES overlay for development purposes.
116
125
  */
117
- function resourcesPluginOverlay({ prefix, prod, outdir, setRunnerEnv, iconsPath: baseIconsPath }) {
126
+ function resourcesPluginOverlay({ prefix, prod, outdir, setRunnerEnv, iconsPath: baseIconsPath, bundle }) {
118
127
  if (prod) return null;
119
128
  const collectedResources = /* @__PURE__ */ new Map();
120
129
  const devResourcesOverlay = path.join(outdir, "_peachy_resources");
@@ -125,13 +134,12 @@ function resourcesPluginOverlay({ prefix, prod, outdir, setRunnerEnv, iconsPath:
125
134
  load: {
126
135
  filter: { id: prefixRegex(`${GRESOURCE_PREFIX}:`) },
127
136
  handler(id) {
128
- const [, importType, path$1] = id.split(":", 3);
129
- if (!path$1) return;
130
- const resourceId = getResourceId(prefix, path$1);
131
- collectedResources.set(path$1, {
137
+ const [, importType, path] = id.split(":", 3);
138
+ if (!path) return;
139
+ const resourceId = getResourceId(prefix, path);
140
+ collectedResources.set(path, {
132
141
  resourceId,
133
- path: path$1,
134
- internalResourceId: getInternalResourceId(path$1)
142
+ internalResourceId: getInternalResourceId(path)
135
143
  });
136
144
  return generateImportCode(importType, resourceId, false);
137
145
  }
@@ -148,6 +156,7 @@ function resourcesPluginOverlay({ prefix, prod, outdir, setRunnerEnv, iconsPath:
148
156
  },
149
157
  async writeBundle() {
150
158
  await fs.mkdir(devResourcesOverlay, { recursive: true });
159
+ addBundledResources(collectedResources, prefix, bundle);
151
160
  const currentPaths = new Set(Array.from(collectedResources.values()).map((r) => r.internalResourceId));
152
161
  const existing = await fs.readdir(devResourcesOverlay, {
153
162
  recursive: true,
@@ -157,10 +166,10 @@ function resourcesPluginOverlay({ prefix, prod, outdir, setRunnerEnv, iconsPath:
157
166
  if (!entry.isSymbolicLink()) continue;
158
167
  const fullPath = path.resolve(entry.parentPath, entry.name);
159
168
  const relativePath = path.relative(devResourcesOverlay, fullPath);
160
- if (currentPaths.has(relativePath) || relativePath === GRESOURCE_ICONS_PATH) continue;
169
+ if (currentPaths.has(relativePath) || relativePath === "icons/scalable/actions") continue;
161
170
  await fs.unlink(fullPath);
162
171
  }
163
- for (const [, { path: filePath, internalResourceId }] of collectedResources) {
172
+ for (const [filePath, { internalResourceId }] of collectedResources) {
164
173
  const linkPath = path.join(devResourcesOverlay, internalResourceId);
165
174
  try {
166
175
  await fs.lstat(linkPath);
@@ -173,11 +182,37 @@ function resourcesPluginOverlay({ prefix, prod, outdir, setRunnerEnv, iconsPath:
173
182
  }
174
183
  };
175
184
  }
176
-
185
+ //#endregion
186
+ //#region src/plugins/loader.ts
187
+ /**
188
+ * This plugin injects some code that loads and registers our application's
189
+ * GResource
190
+ */
191
+ function resourcesPluginLoader({ gresourcePath }) {
192
+ return {
193
+ name: "@peachy/plugin-resources#loader",
194
+ resolveId: {
195
+ filter: { id: exactRegex(GRESOURCE_LOADER) },
196
+ handler(id) {
197
+ return id;
198
+ }
199
+ },
200
+ load: {
201
+ filter: { id: exactRegex(GRESOURCE_LOADER) },
202
+ handler() {
203
+ return [
204
+ `import Gio from "gi://Gio";`,
205
+ `const resource = Gio.Resource.load("${gresourcePath}");`,
206
+ `resource._register();`
207
+ ].join("\n");
208
+ }
209
+ }
210
+ };
211
+ }
177
212
  //#endregion
178
213
  //#region src/plugins/prod/index.ts
179
214
  const exec$1 = promisify(exec);
180
- function resourcesPluginGenerateXML({ prefix, gresourceName, gresourcePath, prod, iconsPath }) {
215
+ function resourcesPluginGenerateXML({ prefix, gresourceName, gresourcePath, prod, iconsPath, bundle }) {
181
216
  if (!prod) return null;
182
217
  const collectedResources = /* @__PURE__ */ new Map();
183
218
  return {
@@ -185,23 +220,25 @@ function resourcesPluginGenerateXML({ prefix, gresourceName, gresourcePath, prod
185
220
  load: {
186
221
  filter: { id: prefixRegex(`${GRESOURCE_PREFIX}:`) },
187
222
  handler(id) {
188
- const [, _importType, path$1] = id.split(":", 3);
189
- if (!path$1) return;
223
+ const [, _importType, path] = id.split(":", 3);
224
+ if (!path) return;
190
225
  const importType = _importType;
191
- const resourceId = getResourceId(prefix, path$1);
192
- collectedResources.set(path$1, {
226
+ const resourceId = getResourceId(prefix, path);
227
+ collectedResources.set(path, {
193
228
  resourceId,
194
- path: path$1,
195
- internalResourceId: getInternalResourceId(path$1)
229
+ internalResourceId: getInternalResourceId(path)
196
230
  });
197
231
  return generateImportCode(importType, resourceId, true);
198
232
  }
199
233
  },
200
234
  async generateBundle() {
201
235
  const collectedIcons = await collectIcons(iconsPath ?? false, prefix);
236
+ addBundledResources(collectedResources, prefix, bundle);
202
237
  if (collectedResources.size === 0 && collectedIcons.size === 0) return;
203
238
  const xmlPath = join((await mkdtempDisposable(join(tmpdir(), "peachy-resource-build"))).path, "gresources.xml");
204
- await writeFile(xmlPath, generateGResourceXML(prefix, collectedResources, collectedIcons));
239
+ const xml = generateGResourceXML(prefix, collectedResources, collectedIcons);
240
+ await writeFile(xmlPath, xml);
241
+ console.log("xml", xml);
205
242
  await mkdir(dirname(gresourcePath), { recursive: true });
206
243
  await exec$1(`glib-compile-resources --sourcedir=${process.cwd()} --target=${gresourcePath} ${xmlPath}`, { env: { ...process.env } });
207
244
  this.emitFile({
@@ -212,7 +249,6 @@ function resourcesPluginGenerateXML({ prefix, gresourceName, gresourcePath, prod
212
249
  }
213
250
  };
214
251
  }
215
-
216
252
  //#endregion
217
253
  //#region src/plugins/resolve.ts
218
254
  /**
@@ -233,35 +269,6 @@ function resourcesPluginResolve() {
233
269
  }
234
270
  };
235
271
  }
236
-
237
- //#endregion
238
- //#region src/plugins/loader.ts
239
- /**
240
- * This plugin injects some code that loads and registers our application's
241
- * GResource
242
- */
243
- function resourcesPluginLoader({ gresourcePath }) {
244
- return {
245
- name: "@peachy/plugin-resources#loader",
246
- resolveId: {
247
- filter: { id: exactRegex(GRESOURCE_LOADER) },
248
- handler(id) {
249
- return id;
250
- }
251
- },
252
- load: {
253
- filter: { id: exactRegex(GRESOURCE_LOADER) },
254
- handler() {
255
- return [
256
- `import Gio from "gi://Gio";`,
257
- `const resource = Gio.Resource.load("${gresourcePath}");`,
258
- `resource._register();`
259
- ].join("\n");
260
- }
261
- }
262
- };
263
- }
264
-
265
272
  //#endregion
266
273
  //#region src/plugins/index.ts
267
274
  function resourcesPlugin(config) {
@@ -282,6 +289,5 @@ function resourcesPlugin(config) {
282
289
  resourcesPluginGenerateXML(resourcesOptions)
283
290
  ].filter(Boolean);
284
291
  }
285
-
286
292
  //#endregion
287
- export { resourcesPlugin };
293
+ export { resourcesPlugin };
@@ -1,4 +1,4 @@
1
- //#region src/types/global.d.ts
1
+ //#region src/modules.d.ts
2
2
  declare module "*.svg" {
3
3
  const content: string;
4
4
  export default content;
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
1
  {
2
2
  "name": "@peachy/plugin-resources",
3
- "version": "0.0.10",
4
- "type": "module",
3
+ "version": "0.0.12",
5
4
  "description": "Import resources in your app",
6
- "main": "./src/index.ts",
5
+ "license": "MIT",
6
+ "author": "",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "type": "module",
7
11
  "exports": {
8
12
  ".": {
9
13
  "import": "./dist/index.mjs",
10
14
  "types": "./dist/index.d.mts"
11
15
  },
12
- "./types": {
13
- "types": "./dist/types/global.d.mts"
16
+ "./modules": {
17
+ "types": "./dist/modules.d.mts"
14
18
  }
15
19
  },
16
- "author": "",
17
- "license": "MIT",
18
20
  "devDependencies": {
19
- "@types/node": "^25.0.9",
20
- "rolldown": "1.0.0-beta.60",
21
- "tsdown": "0.20.0-beta.4",
22
- "typescript": "^5.9.3"
21
+ "@types/node": "^25.3.5",
22
+ "rolldown": "1.0.0-rc.7",
23
+ "tsdown": "0.21.0",
24
+ "typescript": "^5.9.3",
25
+ "@peachy/internal-utilities": "0.0.0"
23
26
  },
24
27
  "peerDependencies": {
25
28
  "rolldown": "1.0.0-beta.58"
26
29
  },
27
- "files": [
28
- "dist"
29
- ],
30
30
  "scripts": {
31
- "build": "tsdown src/index.ts src/types/global.d.ts --dts"
31
+ "build": "tsdown src/index.ts src/modules.d.ts --dts"
32
32
  }
33
33
  }
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./plugins";