@aippy/vite-plugins 0.4.0-dev.2 → 0.4.0-dev.4

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
@@ -102,10 +102,12 @@ const projectRoot = findProjectRoot(); // '/path/to/project'
102
102
 
103
103
  UNLICENSED - Aippy Team
104
104
 
105
- ## Asset manifest v1
105
+ ## Asset files
106
106
 
107
- Version 0.4.0-dev.1 reads both legacy flat URL inventories and `schemaVersion: 1` manifests. Assets Agent writes resource plans; development builds preserve their metadata and supplement resources discovered by the existing scanner. Invalid v1 inventories and manifest/wrapper write failures stop the build.
107
+ `src/config/assets.json` is a flat URL dictionary. The plugin writes only that file. The project-root `assets-plan.json` is read-only input for reserved names and `implementation.path` exclusions. The plugin does not validate the plan; aippy-chat does. A missing or unreadable plan does not stop the build. Invalid dictionary JSON, a single-file v1 plan stored in the dictionary, or a failed dictionary/wrapper write stops the build without overwriting the file. A source file the plugin cannot parse or rewrite only logs a warning.
108
108
 
109
- Import the generated flat URL map from `@/config/assets`. Only entries with usable URLs appear in that map; the existing `window.getReplacedAssets` hook still mutates it in place. Source-code assets and generated fallbacks with an exact `implementation.path` retain their original source references. Preload reads resource URLs rather than generation prompts or error text. The JSON schema is exported as `@aippy/vite-plugins/assets.schema.json`; parser checks also enforce byte, reference and semantic constraints.
109
+ Import the generated map from `@/config/assets`. It exports the dictionary's string values. `window.getReplacedAssets` still mutates that object in place. Preload reads those string values and URLs in other source files, not prompts inside the plan.
110
110
 
111
- Pending the next dev release, wrappers retain only historical flat placeholder keys listed in `legacyPlaceholderKeys` as empty strings; other unavailable media remain absent. Concrete code assets continue to use their source references. These changes are not included in the published 0.4.0-dev.1 package.
111
+ Source written as `aippy-asset:` + `encodeURIComponent(key)` + `:` + url rewrites to that dictionary key. The url may be any reference aippy-chat accepts (`ValidReference`): http(s), root-relative, or relative. A token the rewrite cannot reach (template literal, CSS, HTML, unknown key) is reduced to its plain url, on disk in `devMode` and in every build's `transform`, so a token never reaches the browser.
112
+
113
+ Version 0.4.0-dev.3 is published. The `dev` dist-tag points at it. `latest` remains `0.3.0`.
@@ -1,11 +1,6 @@
1
1
  /**
2
2
  * HTML processing utilities for aippy preload plugin
3
3
  */
4
- /**
5
- * Generate meta tags for asset preloading
6
- * @param assets - Array of asset URLs
7
- * @returns HTML meta tags string
8
- */
9
4
  export declare function generateMetaTags(assets: string[]): string;
10
5
  /**
11
6
  * Inject meta tags into HTML content
@@ -1,2 +1,2 @@
1
- import { t as e } from "../aippy-preload-Dhhw3O6d.js";
1
+ import { t as e } from "../aippy-preload-BjhVTEKH.js";
2
2
  export { e as aippyPreloadPlugin, e as default };
@@ -0,0 +1,175 @@
1
+ import { extractAssetsFromContent as e, isValidAsset as t } from "./utils/index.js";
2
+ import { l as n, m as r, s as i } from "./generator-utils-VznlFF00.js";
3
+ import { promises as a } from "fs";
4
+ import o from "path";
5
+ //#region src/aippy-preload/scanner-utils.ts
6
+ async function s(t) {
7
+ let n = [], r = [o.join(process.cwd(), "index.html")];
8
+ for (let o of r) try {
9
+ let r = i(await a.readFile(o, "utf-8")), s = e(r, "html", t);
10
+ n.push(...s);
11
+ } catch {
12
+ console.warn("⚠️ No index.html found in project root");
13
+ }
14
+ return n;
15
+ }
16
+ async function c(t, s) {
17
+ let c = [], l = o.resolve(t, "config/assets.json"), u = /* @__PURE__ */ new Set();
18
+ try {
19
+ let e = JSON.parse(await a.readFile(l, "utf8"));
20
+ e && typeof e == "object" && !Array.isArray(e) && (u = new Set(Object.keys(e)));
21
+ } catch {}
22
+ let d = await r(t, [
23
+ ".ts",
24
+ ".tsx",
25
+ ".js",
26
+ ".jsx"
27
+ ]);
28
+ for (let r of d) try {
29
+ let o = i(await a.readFile(r, "utf-8")), d = e(o, "js", s);
30
+ d.some((e) => u.has(e)) && (d = e(n(o, r, t, l), "js", s)), c.push(...d);
31
+ } catch (e) {
32
+ console.warn(`⚠️ Failed to read JS file: ${r}`, e);
33
+ }
34
+ return c;
35
+ }
36
+ async function l(t, n) {
37
+ let o = [], s = await r(t, [
38
+ ".css",
39
+ ".scss",
40
+ ".sass",
41
+ ".less"
42
+ ]);
43
+ for (let t of s) try {
44
+ let r = i(await a.readFile(t, "utf-8")), s = e(r, "css", n);
45
+ o.push(...s);
46
+ } catch (e) {
47
+ console.warn(`⚠️ Failed to read CSS file: ${t}`, e);
48
+ }
49
+ return o;
50
+ }
51
+ function u(e, n) {
52
+ if (!t(e) || /["'<>\s`]/.test(e)) return !1;
53
+ let r = e.split(/[?#]/, 1)[0].toLowerCase();
54
+ return n.some((e) => r.endsWith(e.toLowerCase()));
55
+ }
56
+ async function d(t, n) {
57
+ let s = [], c = await r(t, [".json"]);
58
+ for (let r of c) try {
59
+ let c = i(await a.readFile(r, "utf-8"));
60
+ if (o.basename(r) === "assets-plan.json") continue;
61
+ if (o.resolve(r) === o.resolve(t, "config/assets.json")) {
62
+ let e = JSON.parse(c);
63
+ if (e && typeof e == "object" && !Array.isArray(e)) {
64
+ for (let t of Object.values(e)) typeof t == "string" && u(t, n) && s.push(t);
65
+ continue;
66
+ }
67
+ }
68
+ let l = e(c, "js", n);
69
+ s.push(...l);
70
+ } catch (e) {
71
+ console.warn(`⚠️ Failed to read JSON file: ${r}`, e);
72
+ }
73
+ return s;
74
+ }
75
+ async function f(e, t, n) {
76
+ console.log("🔍 Scanning source files for static assets...");
77
+ let r = [];
78
+ if (n) {
79
+ console.log("🔍 Deep scanning source file contents...");
80
+ let [n, i, a, o] = await Promise.all([
81
+ s(t),
82
+ c(e, t),
83
+ l(e, t),
84
+ d(e, t)
85
+ ]);
86
+ r.push(...n, ...i, ...a, ...o);
87
+ }
88
+ return [...new Set(r)];
89
+ }
90
+ //#endregion
91
+ //#region src/aippy-preload/html-utils.ts
92
+ var p = "<head>", m = 6;
93
+ function h(e) {
94
+ return e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
95
+ }
96
+ function g(e) {
97
+ return e.map((e) => ` <meta content="${h(e)}" name="aippy:preload">`).join("\n");
98
+ }
99
+ function _(e, t) {
100
+ let n = e.indexOf(p);
101
+ if (n === -1) throw Error("Could not find <head> tag in HTML");
102
+ let r = n + m;
103
+ return e.slice(0, r) + "\n" + t + "\n" + e.slice(r);
104
+ }
105
+ function v(e) {
106
+ let t = e.length;
107
+ if (console.log(`📁 Found ${t} static ${t === 1 ? "asset" : "assets"} in source files`), t === 0) {
108
+ console.log("⚠️ No static assets found in source files.");
109
+ return;
110
+ }
111
+ console.log(`📋 ${t === 1 ? "Asset" : "Assets"} found:`), e.forEach((e) => console.log(` - ${e}`));
112
+ }
113
+ function y(e) {
114
+ console.log(`✅ Injected ${e} preload meta ${e === 1 ? "tag" : "tags"} into index.html`);
115
+ }
116
+ //#endregion
117
+ //#region src/aippy-preload/index.ts
118
+ var b = 3, x = 1e3;
119
+ function S(e) {
120
+ return new Promise((t) => setTimeout(t, e));
121
+ }
122
+ async function C(e, t = b, n = x) {
123
+ for (let r = 1; r <= t; r++) try {
124
+ return await a.readFile(e, "utf-8");
125
+ } catch (e) {
126
+ if (e.code === "ENOENT") {
127
+ if (r < t) {
128
+ console.log(`⏳ index.html not found, waiting ${n}ms before retry (attempt ${r}/${t})...`), await S(n);
129
+ continue;
130
+ }
131
+ return console.log(`⚠️ index.html not found after ${t} attempts, skipping preload injection.`), null;
132
+ }
133
+ return console.error("❌ Error reading index.html:", e), null;
134
+ }
135
+ return null;
136
+ }
137
+ function w(e = {}) {
138
+ let t = {
139
+ extensions: [
140
+ ".png",
141
+ ".jpg",
142
+ ".jpeg",
143
+ ".gif",
144
+ ".svg",
145
+ ".mp4",
146
+ ".mp3",
147
+ ".wav",
148
+ ".ogg",
149
+ ".webm"
150
+ ],
151
+ srcDir: "src",
152
+ outDir: "dist",
153
+ deepScan: !0,
154
+ ...e
155
+ };
156
+ return {
157
+ name: "vite-plugin-aippy-preload",
158
+ apply: "build",
159
+ enforce: "post",
160
+ async writeBundle() {
161
+ try {
162
+ let e = await f(t.srcDir, t.extensions, t.deepScan);
163
+ if (v(e), e.length === 0) return;
164
+ let n = o.join(t.outDir, "index.html"), r = await C(n);
165
+ if (r === null) return;
166
+ let i = _(r, g(e));
167
+ await a.writeFile(n, i, "utf-8"), y(e.length);
168
+ } catch (e) {
169
+ console.error("❌ Error in aippy preload plugin:", e);
170
+ }
171
+ }
172
+ };
173
+ }
174
+ //#endregion
175
+ export { w as t };
@@ -1,12 +1,38 @@
1
1
  import { AssetInfo } from './types';
2
- import { AssetDocument } from './manifest';
2
+ /** A flat dictionary is a v1 plan only when schemaVersion is a number and assets is an object. */
3
+ export declare function isSingleFilePlan(value: unknown): boolean;
4
+ export declare const ASSET_TOKEN_PREFIX = "aippy-asset:";
3
5
  /**
4
- * Generate assets.json file content from asset info
5
- * @param assets - Map of assets
6
- * @param existingAssets - Map of existing assets from previous runs
7
- * @returns JSON string
6
+ * Mirrors aippy-chat `assetmanifest.ValidReference`: every value the server
7
+ * puts in the inventory as a usable url must be accepted here, including
8
+ * root-relative and relative paths.
8
9
  */
9
- export declare function generateAssetsFileContent(assets: Map<string, AssetInfo>, existingAssets: Map<string, string>, document?: AssetDocument): string;
10
+ export declare function isDictionaryResourceURL(value: string): boolean;
11
+ /** Keys that own each usable dictionary URL. A shared URL lists every key. */
12
+ export declare function dictionaryResourceOwners(flat: Record<string, unknown>): Map<string, string[]>;
13
+ /** Percent-encode a dictionary key so `:` and `$` cannot be read as separators. */
14
+ export declare function encodeAssetKey(key: string): string;
15
+ /**
16
+ * `aippy-asset:<encodeURIComponent(key)>:<url>`.
17
+ * The key ends at the first raw colon. `hero:mobile` must be `hero%3Amobile`;
18
+ * splitting that unencoded form would name `hero` and is rejected because the
19
+ * remainder `mobile:https://...` is not a resource reference.
20
+ */
21
+ export declare function parseAssetToken(value: string): {
22
+ key: string;
23
+ url: string;
24
+ } | undefined;
25
+ /**
26
+ * Drop the `aippy-asset:<key>:` prefix from any token the key rewrite did not
27
+ * reach (template literals, CSS, HTML, unknown keys), leaving the url itself.
28
+ * A literal token is never a loadable resource.
29
+ */
30
+ export declare function stripAssetTokens(code: string): string;
31
+ /**
32
+ * Write the flat dictionary. Existing keys and non-string values stay; scanned
33
+ * resources only add a key when that URL is not already present.
34
+ */
35
+ export declare function generateAssetsFileContent(assets: Map<string, AssetInfo>, existingAssets: Map<string, string>, flat?: Record<string, unknown>): string;
10
36
  /**
11
37
  * Parse existing assets.json file
12
38
  * @param content - File content
@@ -1,2 +1,391 @@
1
- import { t as e } from "../asset-constants-BUK7zWRd.js";
2
- export { e as assetConstantsPlugin, e as default };
1
+ import { extractAssetsFromContent as e, generateConstantName as t, processAssetURL as n } from "../utils/index.js";
2
+ import { a as r, c as i, d as a, f as o, g as s, h as c, i as l, l as u, m as d, n as f, o as p, p as m, r as h, s as g, t as _, u as v } from "../generator-utils-VznlFF00.js";
3
+ import { promises as y } from "fs";
4
+ import b from "path";
5
+ import { parse as x } from "@babel/parser";
6
+ import S from "@babel/traverse";
7
+ import * as C from "@babel/types";
8
+ import w from "@babel/generator";
9
+ //#region src/asset-constants/index.ts
10
+ var T = typeof S == "function" ? S : S.default, E = typeof w == "function" ? w : w.default, D = class extends Error {}, O = class {
11
+ constructor(e = {}) {
12
+ s(this, "options", void 0), s(this, "assets", /* @__PURE__ */ new Map()), s(this, "existingAssets", /* @__PURE__ */ new Map()), s(this, "flat", {}), s(this, "fileCache", new o()), s(this, "planKeys", void 0), s(this, "implementationPaths", /* @__PURE__ */ new Set()), s(this, "keyRefFiles", /* @__PURE__ */ new Set()), this.options = {
13
+ extensions: [
14
+ ".png",
15
+ ".jpg",
16
+ ".jpeg",
17
+ ".gif",
18
+ ".svg",
19
+ ".mp4",
20
+ ".mp3",
21
+ ".wav",
22
+ ".ogg",
23
+ ".webm"
24
+ ],
25
+ srcDir: "src",
26
+ outputFile: "src/config/assets.json",
27
+ devMode: !1,
28
+ ...e
29
+ };
30
+ }
31
+ clearCache() {
32
+ this.fileCache.clear();
33
+ }
34
+ generateConstantName(e) {
35
+ let n = /* @__PURE__ */ new Set([
36
+ ...Array.from(this.assets.values()).map((e) => e.constantName),
37
+ ...Array.from(this.existingAssets.values()),
38
+ ...this.planKeys ?? []
39
+ ]);
40
+ return t(e, n);
41
+ }
42
+ async scanTsFile(t) {
43
+ try {
44
+ let n = await this.fileCache.read(t), r = [], i = e(n, "js", this.options.extensions), a = /* @__PURE__ */ new Set([...Object.keys(this.flat), ...this.planKeys ?? []]);
45
+ if (i.some((e) => a.has(e))) {
46
+ let r = u(n, t, this.options.srcDir, this.options.outputFile);
47
+ i = e(r, "js", this.options.extensions);
48
+ }
49
+ let o = n.split("\n"), s = _(this.flat);
50
+ i = i.filter((e) => {
51
+ if (e.startsWith("aippy-asset:") || !n.includes(`"${e}"`) && !n.includes(`'${e}'`)) return !1;
52
+ let t = s.get(e);
53
+ return !t || t.length === 1;
54
+ }), i.forEach((e) => {
55
+ if (this.isImplementationReference(t, e)) return;
56
+ let n = this.generateConstantName(e), i = o.findIndex((t) => t.includes(e)) + 1;
57
+ r.push({
58
+ originalUri: e,
59
+ constantName: n,
60
+ filePath: t,
61
+ line: i,
62
+ column: 1
63
+ });
64
+ }), n.includes("aippy-asset:") && this.keyRefFiles.add(t);
65
+ let c = new Set(r.map((e) => e.originalUri));
66
+ for (let [e, i] of s) i.length !== 1 || c.has(e) || !n.includes(`"${e}"`) && !n.includes(`'${e}'`) || r.push({
67
+ originalUri: e,
68
+ constantName: i[0],
69
+ filePath: t,
70
+ line: 1,
71
+ column: 1
72
+ });
73
+ return r;
74
+ } catch (e) {
75
+ return console.warn(`Warning: Failed to scan ${t}:`, e), [];
76
+ }
77
+ }
78
+ async scanAllTsFiles() {
79
+ let e = this.options.srcDir;
80
+ try {
81
+ let t = await d(e, [".ts", ".tsx"]);
82
+ for (let e of t) {
83
+ if (e.endsWith("assets.json") || e.endsWith("assets.ts")) continue;
84
+ let t = await this.scanTsFile(e), n = _(this.flat);
85
+ t.forEach((e) => {
86
+ let t = n.get(e.originalUri);
87
+ if (t && t.length > 1) return;
88
+ t && t.length === 1 ? e.constantName = t[0] : this.existingAssets.has(e.originalUri) && (e.constantName = this.existingAssets.get(e.originalUri));
89
+ let r = e.originalUri;
90
+ this.assets.has(r) || this.assets.set(r, e);
91
+ });
92
+ }
93
+ } catch (e) {
94
+ console.error("Error scanning TypeScript files:", e);
95
+ }
96
+ }
97
+ async readExistingAssetsFile() {
98
+ try {
99
+ return await y.readFile(this.options.outputFile, "utf-8");
100
+ } catch (e) {
101
+ if (e.code === "ENOENT") return "";
102
+ throw new D("Unable to read assets.json: " + String(e));
103
+ }
104
+ }
105
+ sidecarPath() {
106
+ return b.resolve(this.options.srcDir, "..", "assets-plan.json");
107
+ }
108
+ async parseExistingAssets() {
109
+ let e = await this.readExistingAssetsFile();
110
+ if (e.trim()) {
111
+ let t;
112
+ try {
113
+ t = JSON.parse(e);
114
+ } catch {
115
+ throw new D("assets: invalid JSON syntax");
116
+ }
117
+ if (h(t)) throw new D("assets: single-file asset plan is not supported");
118
+ if (!t || typeof t != "object" || Array.isArray(t)) throw new D("assets: URL dictionary object required");
119
+ this.flat = { ...t };
120
+ }
121
+ try {
122
+ this.existingAssets = p(e);
123
+ } catch (e) {
124
+ throw new D(e instanceof Error ? e.message : "assets: invalid JSON syntax");
125
+ }
126
+ await this.readSidecar();
127
+ }
128
+ async readSidecar() {
129
+ let e = "";
130
+ try {
131
+ e = await y.readFile(this.sidecarPath(), "utf-8");
132
+ } catch (e) {
133
+ e.code !== "ENOENT" && console.warn("assets: unable to read assets-plan.json; continuing with the dictionary only");
134
+ return;
135
+ }
136
+ if (!e.trim()) return;
137
+ let t;
138
+ try {
139
+ t = JSON.parse(e);
140
+ } catch {
141
+ console.warn("assets: assets-plan.json is not valid JSON; continuing with the dictionary only");
142
+ return;
143
+ }
144
+ if (!h(t)) {
145
+ console.warn("assets: assets-plan.json is not a plan; continuing with the dictionary only");
146
+ return;
147
+ }
148
+ let n = t.assets;
149
+ this.planKeys = Object.keys(n);
150
+ let r = b.resolve(this.options.srcDir, "..");
151
+ for (let e of Object.values(n)) {
152
+ let t = e?.implementation?.path;
153
+ typeof t == "string" && t && this.implementationPaths.add(b.resolve(r, t));
154
+ }
155
+ }
156
+ isImplementationReference(e, t) {
157
+ if (!this.implementationPaths.size || /^[a-z][a-z0-9+.-]*:/i.test(t) || t.startsWith("//")) return !1;
158
+ let n = t.split(/[?#]/)[0], r = n.startsWith("@/") ? b.resolve(this.options.srcDir, n.slice(2)) : b.resolve(b.dirname(e), n);
159
+ return this.implementationPaths.has(r);
160
+ }
161
+ async generateAssetsFile() {
162
+ await this.parseExistingAssets(), await this.scanAllTsFiles();
163
+ let e = f(this.assets, this.existingAssets, this.flat);
164
+ try {
165
+ let t = b.dirname(this.options.outputFile);
166
+ await y.mkdir(t, { recursive: !0 }), await y.writeFile(this.options.outputFile, e, "utf-8"), await this.generateAssetsWrapper();
167
+ } catch (e) {
168
+ throw new D("Unable to write the asset dictionary or wrapper: " + String(e));
169
+ }
170
+ l(this.assets.size, this.existingAssets, this.assets, this.options.outputFile);
171
+ }
172
+ async generateAssetsWrapper() {
173
+ let e = this.options.outputFile.replace(/\.json$/, ".ts"), t = `// 🍎 Auto-generated wrapper for assets.json
174
+ // This file handles iOS image replacement via asset-interceptor.js
175
+ import assetsData from "./${b.basename(this.options.outputFile)}";
176
+
177
+ // Batch process all asset URLs for potential replacements
178
+ // sessionStorage may contain __aippy_asset_overrides__ with multiple image mappings
179
+ (window as any).getReplacedAssets?.(assetsData);
180
+
181
+ export default assetsData;
182
+ `;
183
+ await y.writeFile(e, t, "utf-8"), console.log(` ✓ Generated assets wrapper: ${b.relative(process.cwd(), e)}`);
184
+ }
185
+ getAssets() {
186
+ return Array.from(this.assets.values());
187
+ }
188
+ isReplacementCandidate(e, t) {
189
+ let i = r(e);
190
+ return i && Object.hasOwn(this.flat, i.key) ? !0 : t.has(n(e));
191
+ }
192
+ replaceLiteral(e, t, n) {
193
+ let r = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(n), i = C.memberExpression(C.identifier(t), r ? C.identifier(n) : C.stringLiteral(n), !r);
194
+ if ((C.isObjectProperty(e.parent) || C.isObjectMethod(e.parent) || C.isClassProperty(e.parent) || C.isClassMethod(e.parent)) && e.key === "key" && (e.parent.computed = !0), e.parent && C.isJSXAttribute(e.parent)) return e.replaceWith(C.jsxExpressionContainer(i)), !0;
195
+ try {
196
+ C.validate(e.parent, e.key, i);
197
+ } catch {
198
+ return !1;
199
+ }
200
+ return e.replaceWith(i), !0;
201
+ }
202
+ async replaceUrisInFile(e, t) {
203
+ try {
204
+ let o = await this.fileCache.read(e), s = x(o, {
205
+ sourceType: "module",
206
+ plugins: [
207
+ "typescript",
208
+ "jsx",
209
+ "decorators-legacy"
210
+ ]
211
+ }), c = !1, l = this.options.outputFile.replace(/\.json$/, ".ts"), u = m(l, this.options.srcDir), d = v(e, this.options.srcDir, this.options.outputFile), f = a(s, d), p = "assetsData", h = !1, g, _ = /* @__PURE__ */ new Set();
212
+ T(s, {
213
+ Identifier: (e) => {
214
+ _.add(e.node.name);
215
+ },
216
+ ImportDeclaration: (e) => {
217
+ if (!(!d.has(e.node.source.value) || e.node.importKind === "type")) for (let t of e.node.specifiers) {
218
+ if (t.importKind === "type") continue;
219
+ let n = e.scope.getBinding(t.local.name);
220
+ i(t) && (p = t.local.name, h = !0, g = n);
221
+ }
222
+ }
223
+ });
224
+ let S = !1;
225
+ if (h && T(s, { StringLiteral: (n) => {
226
+ !C.isImportDeclaration(n.parent) && !f(n) && !this.isImplementationReference(e, n.node.value) && this.isReplacementCandidate(n.node.value, t) && n.scope.getBinding(p) !== g && (S = !0);
227
+ } }), !h && _.has(p) || S) {
228
+ let e = "aippyAssetsData";
229
+ for (let t = 2; _.has(e); t++) e = `aippyAssetsData${t}`;
230
+ S && g.scope.rename(p, e), p = e;
231
+ }
232
+ if (T(s, { StringLiteral: (i) => {
233
+ let a = i.node.value;
234
+ if (this.isImplementationReference(e, a) || f(i) || C.isImportDeclaration(i.parent) || C.isExportNamedDeclaration(i.parent) || C.isExportAllDeclaration(i.parent) || i.findParent((e) => C.isTSType(e.node) || C.isTSInterfaceBody(e.node))) return;
235
+ let o = r(a);
236
+ if (o && Object.hasOwn(this.flat, o.key)) {
237
+ this.replaceLiteral(i, p, o.key) && (i.skip(), c = !0);
238
+ return;
239
+ }
240
+ let s = n(a);
241
+ if (t.has(s)) {
242
+ let e = t.get(s);
243
+ this.replaceLiteral(i, p, e) && (i.skip(), c = !0);
244
+ }
245
+ } }), !c) return;
246
+ if (!h) {
247
+ let e = C.importDeclaration([C.importDefaultSpecifier(C.identifier(p))], C.stringLiteral(u));
248
+ s.program.body.unshift(e);
249
+ }
250
+ let w = E(s, {
251
+ retainLines: !1,
252
+ comments: !0,
253
+ compact: !1
254
+ }, o);
255
+ await y.writeFile(e, w.code, "utf-8"), this.fileCache.invalidate(e), console.log(` ✓ Replaced URIs in ${b.relative(process.cwd(), e)}`);
256
+ } catch (t) {
257
+ console.warn(`Warning: Failed to replace URIs in ${e}:`, t);
258
+ }
259
+ }
260
+ async replaceAllUris() {
261
+ let e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Set();
262
+ for (let n of this.assets.values()) e.set(n.originalUri, n.constantName), t.add(n.filePath);
263
+ for (let e of this.keyRefFiles) t.add(e);
264
+ for (let n of t) await this.replaceUrisInFile(n, e);
265
+ }
266
+ needsRewrite() {
267
+ return this.assets.size > 0 || this.keyRefFiles.size > 0;
268
+ }
269
+ async cleanupUnusedConstants() {
270
+ if (!this.planKeys) try {
271
+ let e = await this.readExistingAssetsFile();
272
+ if (!e.trim()) return;
273
+ let t = /* @__PURE__ */ new Map();
274
+ try {
275
+ let n = JSON.parse(e);
276
+ for (let [e, r] of Object.entries(n)) typeof r == "string" && t.set(e, r);
277
+ } catch (e) {
278
+ console.warn("Warning: Failed to parse assets.json:", e);
279
+ return;
280
+ }
281
+ if (t.size === 0) return;
282
+ let n = /* @__PURE__ */ new Set(), r = await d(this.options.srcDir, [".ts", ".tsx"]);
283
+ for (let e of r) if (!(e.endsWith("assets.json") || e.endsWith("assets.ts"))) try {
284
+ let r = await this.fileCache.read(e);
285
+ for (let e of t.keys()) r.includes(e) && n.add(e);
286
+ } catch (t) {
287
+ console.warn(`Warning: Failed to read ${e}:`, t);
288
+ }
289
+ let i = [];
290
+ for (let e of t.keys()) n.has(e) || i.push(e);
291
+ if (i.length > 0) {
292
+ console.log(`🧹 Removing ${i.length} unused constant(s):`), i.forEach((e) => {
293
+ console.log(` - ${e}`);
294
+ });
295
+ let e = [];
296
+ for (let n of i) {
297
+ let r = t.get(n);
298
+ r && e.push(r);
299
+ }
300
+ for (let t of e) this.assets.delete(t);
301
+ } else console.log("✅ All constants are in use");
302
+ } catch (e) {
303
+ console.warn("Warning: Failed to cleanup unused constants:", e);
304
+ }
305
+ }
306
+ async upgradeJsonImportsToTs() {
307
+ let e = this.options.outputFile.replace(/\.json$/, ".ts"), t = m(e, this.options.srcDir), n = await d(this.options.srcDir, [".ts", ".tsx"]), r = 0;
308
+ for (let i of n) if (!(i.endsWith("assets.ts") || i.endsWith("assets.json"))) try {
309
+ let n = await this.fileCache.read(i), a = c(i, this.options.outputFile) + ".json", o = c(i, e), s = m(this.options.outputFile, this.options.srcDir) + ".json";
310
+ if (!n.includes(a) && !n.includes(o) && !n.includes(s)) continue;
311
+ let l = x(n, {
312
+ sourceType: "module",
313
+ plugins: [
314
+ "typescript",
315
+ "jsx",
316
+ "decorators-legacy"
317
+ ]
318
+ }), u = !1;
319
+ if (T(l, { ImportDeclaration: (e) => {
320
+ let n = e.node.source.value;
321
+ (n === a || n === o || n === s) && (e.node.source.value = t, u = !0);
322
+ } }), u) {
323
+ let e = E(l, {
324
+ retainLines: !1,
325
+ comments: !0,
326
+ compact: !1
327
+ }, n);
328
+ await y.writeFile(i, e.code, "utf-8"), this.fileCache.invalidate(i), console.log(` ✓ Upgraded import in ${b.relative(process.cwd(), i)} → ${t}`), r++;
329
+ }
330
+ } catch (e) {
331
+ console.warn(`Warning: Failed to upgrade imports in ${i}:`, e);
332
+ }
333
+ r > 0 && console.log(`✅ Upgraded ${r} file(s) to use ${t}`);
334
+ }
335
+ async stripUnresolvedTokens() {
336
+ let e = await d(this.options.srcDir, [...k]);
337
+ e.push(b.resolve(this.options.srcDir, "..", "index.html"));
338
+ let t = b.resolve(this.options.outputFile);
339
+ for (let n of e) if (b.resolve(n) !== t) try {
340
+ let e = await y.readFile(n, "utf-8"), t = g(e);
341
+ if (t === e) continue;
342
+ await y.writeFile(n, t, "utf-8"), this.fileCache.invalidate(n), console.log(` ✓ Kept plain asset urls in ${b.relative(process.cwd(), n)}`);
343
+ } catch (e) {
344
+ if (e.code === "ENOENT") continue;
345
+ console.warn(`Warning: Failed to clean asset tokens in ${n}:`, e);
346
+ }
347
+ }
348
+ }, k = [
349
+ ".ts",
350
+ ".tsx",
351
+ ".js",
352
+ ".jsx",
353
+ ".mjs",
354
+ ".css",
355
+ ".scss",
356
+ ".sass",
357
+ ".less",
358
+ ".html",
359
+ ".json"
360
+ ];
361
+ function A(e = {}) {
362
+ return {
363
+ name: "vite-plugin-asset-constants",
364
+ apply: "build",
365
+ enforce: "pre",
366
+ async buildStart() {
367
+ if (!e.devMode) return;
368
+ let t = new O(e);
369
+ try {
370
+ await t.generateAssetsFile();
371
+ let e = t.getAssets();
372
+ t.needsRewrite() && (console.log(`📦 Found ${e.length} asset references:`), e.forEach((e) => {
373
+ console.log(` ${e.constantName} = '${e.originalUri}' (${b.relative(process.cwd(), e.filePath)}:${e.line})`);
374
+ }), console.log("\n🔄 Replacing URIs with constants..."), await t.replaceAllUris(), console.log("✅ URI replacement completed!")), console.log("\n🧹 Checking for unused constants..."), await t.cleanupUnusedConstants(), await t.generateAssetsFile(), console.log("\n🔄 Upgrading assets.json imports to assets.ts..."), await t.upgradeJsonImportsToTs(), await t.stripUnresolvedTokens(), console.log("✅ Asset constants finalized!\n"), t.clearCache();
375
+ } catch (e) {
376
+ console.error("❌ Error in asset constants plugin:", e), e instanceof D && this.error(e.message);
377
+ }
378
+ },
379
+ transform(e, t) {
380
+ return t.includes("/node_modules/") || !e.includes("aippy-asset:") ? null : {
381
+ code: g(e),
382
+ map: null
383
+ };
384
+ },
385
+ transformIndexHtml(e) {
386
+ return g(e);
387
+ }
388
+ };
389
+ }
390
+ //#endregion
391
+ export { A as assetConstantsPlugin, A as default };
@@ -1,5 +1,6 @@
1
1
  import * as t from '@babel/types';
2
2
  export declare function isDefaultImport(item: t.ImportDeclaration['specifiers'][number]): boolean;
3
+ /** A string literal used as `assets['key']` on the wrapper's default import. */
3
4
  export declare function wrapperKeyGuard(ast: t.File, importSources: Set<string>): (p: any) => boolean;
4
5
  export declare function wrapperImportSources(filePath: string, srcDir: string, outputFile: string): Set<string>;
5
6
  export declare function withoutWrapperKeys(content: string, filePath: string, srcDir: string, outputFile: string): string;