@cavulsqa/create 0.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.
Files changed (75) hide show
  1. package/README.md +63 -0
  2. package/bin/create.mjs +142 -0
  3. package/lib/scaffold.mjs +124 -0
  4. package/package.json +60 -0
  5. package/templates/f7-app/.claude/rules/data-fetching.md +67 -0
  6. package/templates/f7-app/.claude/rules/database.md +65 -0
  7. package/templates/f7-app/.claude/rules/framework7-ui.md +79 -0
  8. package/templates/f7-app/.claude/rules/modules.md +43 -0
  9. package/templates/f7-app/.claude/rules/native.md +47 -0
  10. package/templates/f7-app/.claude/skills/f7-design/SKILL.md +67 -0
  11. package/templates/f7-app/.claude/skills/f7-design/components.md +49 -0
  12. package/templates/f7-app/.claude/skills/f7-design/icons.md +52 -0
  13. package/templates/f7-app/.claude/skills/module-architecture/SKILL.md +65 -0
  14. package/templates/f7-app/.claude/skills/module-architecture/file-templates.md +183 -0
  15. package/templates/f7-app/.claude/skills/reactive-data/SKILL.md +89 -0
  16. package/templates/f7-app/.claude/skills/reactive-data/testing.md +55 -0
  17. package/templates/f7-app/CLAUDE.md +105 -0
  18. package/templates/f7-app/auto-imports.d.ts +667 -0
  19. package/templates/f7-app/capacitor.config.ts +14 -0
  20. package/templates/f7-app/components.d.ts +59 -0
  21. package/templates/f7-app/index.html +15 -0
  22. package/templates/f7-app/package.json +51 -0
  23. package/templates/f7-app/src/App.vue +72 -0
  24. package/templates/f7-app/src/app/tabs.ts +25 -0
  25. package/templates/f7-app/src/assets/css/app.css +1 -0
  26. package/templates/f7-app/src/assets/css/icons.css +50 -0
  27. package/templates/f7-app/src/assets/fonts/material-icons-outlined.woff2 +0 -0
  28. package/templates/f7-app/src/assets/fonts/material-icons-round.woff2 +0 -0
  29. package/templates/f7-app/src/domains/sales/sales.repository.ts +458 -0
  30. package/templates/f7-app/src/env.d.ts +23 -0
  31. package/templates/f7-app/src/locales/en.json +179 -0
  32. package/templates/f7-app/src/locales/fr.json +179 -0
  33. package/templates/f7-app/src/main.ts +50 -0
  34. package/templates/f7-app/src/modules/demo/components/DemoBusLog.vue +26 -0
  35. package/templates/f7-app/src/modules/demo/components/DemoCreateOrderSheet.vue +160 -0
  36. package/templates/f7-app/src/modules/demo/components/DemoOrderList.vue +73 -0
  37. package/templates/f7-app/src/modules/demo/components/DemoPipelineBenchmark.vue +52 -0
  38. package/templates/f7-app/src/modules/demo/components/DemoStatCards.vue +63 -0
  39. package/templates/f7-app/src/modules/demo/composables/useReactiveDemo.ts +168 -0
  40. package/templates/f7-app/src/modules/demo/router/routes/demo.routes.ts +34 -0
  41. package/templates/f7-app/src/modules/demo/views/DemoView.vue +126 -0
  42. package/templates/f7-app/src/modules/demo/views/OrderDetailView.vue +131 -0
  43. package/templates/f7-app/src/modules/demo/views/OrderSearchView.vue +106 -0
  44. package/templates/f7-app/src/modules/home/composables/useHomeFeatures.ts +133 -0
  45. package/templates/f7-app/src/modules/home/router/routes/home.routes.ts +29 -0
  46. package/templates/f7-app/src/modules/home/views/FeatureDetailView.vue +108 -0
  47. package/templates/f7-app/src/modules/home/views/HomeView.vue +53 -0
  48. package/templates/f7-app/src/modules/settings/router/routes/settings.routes.ts +16 -0
  49. package/templates/f7-app/src/modules/settings/views/SettingsView.vue +126 -0
  50. package/templates/f7-app/src/plugins/capacitor/index.ts +14 -0
  51. package/templates/f7-app/src/plugins/capacitor/useAndroidBackButton.ts +68 -0
  52. package/templates/f7-app/src/plugins/capacitor/useKeyboard.ts +61 -0
  53. package/templates/f7-app/src/plugins/capacitor/useSplashScreen.ts +11 -0
  54. package/templates/f7-app/src/plugins/capacitor/useStatusBar.ts +15 -0
  55. package/templates/f7-app/src/plugins/framework7.plugin.ts +41 -0
  56. package/templates/f7-app/src/plugins/i18n.plugin.ts +10 -0
  57. package/templates/f7-app/src/plugins/seed.plugin.ts +17 -0
  58. package/templates/f7-app/src/plugins/sqlite.plugin.ts +21 -0
  59. package/templates/f7-app/src/router/global/global.routes.ts +16 -0
  60. package/templates/f7-app/src/router/index.ts +18 -0
  61. package/templates/f7-app/src/shared/components/error/404.vue +12 -0
  62. package/templates/f7-app/src/shared/components/metrics/MetricsPanel.vue +79 -0
  63. package/templates/f7-app/src/shared/composables/theme/useAppTheme.ts +68 -0
  64. package/templates/f7-app/src/shared/composables/useTabbarVisibility.ts +50 -0
  65. package/templates/f7-app/src/shared/database/database.ts +86 -0
  66. package/templates/f7-app/src/shared/database/index.ts +3 -0
  67. package/templates/f7-app/src/shared/database/migrations.ts +81 -0
  68. package/templates/f7-app/src/shared/database/queries.ts +22 -0
  69. package/templates/f7-app/src/shared/database/schema.ts +59 -0
  70. package/templates/f7-app/src/shared/utils/resolvers/resolvers.ts +152 -0
  71. package/templates/f7-app/tests/icons.test.ts +81 -0
  72. package/templates/f7-app/tests/sales.repository.test.ts +329 -0
  73. package/templates/f7-app/tsconfig.json +21 -0
  74. package/templates/f7-app/tsconfig.node.json +14 -0
  75. package/templates/f7-app/vite.config.ts +95 -0
@@ -0,0 +1,329 @@
1
+ import { beforeEach, expect, test } from "vite-plus/test";
2
+ import { Kysely } from "kysely";
3
+ import { Migrator } from "kysely/migration";
4
+ import { createSqlJsDialect } from "@cavulsqa/mobile-db/testing";
5
+ import {
6
+ advanceOrderStatus,
7
+ clearAll,
8
+ createOrder,
9
+ ensureReferenceData,
10
+ listCustomers,
11
+ loadDashboardStats,
12
+ searchOrders,
13
+ seedSampleData,
14
+ deleteOrder,
15
+ nextOrderReference,
16
+ saveOrder,
17
+ setOrderStatus,
18
+ loadOrderDetail,
19
+ } from "../src/domains/sales/sales.repository.js";
20
+ import { migrations } from "../src/shared/database/migrations.js";
21
+ import type { Database } from "../src/shared/database/schema.js";
22
+
23
+ /**
24
+ * The queries run against real SQLite here - sql.js, the same dialect the packages use for their own
25
+ * tests - so the joins, the aggregates and the migration are executed rather than just type-checked.
26
+ * A schema this template ships broken would cost whoever generates from it an afternoon.
27
+ */
28
+ let db: Kysely<Database>;
29
+
30
+ beforeEach(async () => {
31
+ db = new Kysely<Database>({ dialect: await createSqlJsDialect() });
32
+ await new Migrator({
33
+ db,
34
+ provider: { getMigrations: () => Promise.resolve(migrations) },
35
+ }).migrateToLatest();
36
+ });
37
+
38
+ test("the migration creates every table the schema declares", async () => {
39
+ const tables = await db.introspection.getTables();
40
+ const names = tables.map((table) => table.name).sort();
41
+
42
+ expect(names).toContain("customer");
43
+ expect(names).toContain("product");
44
+ expect(names).toContain("sales_order");
45
+ expect(names).toContain("order_line");
46
+ expect(names).toContain("tag");
47
+ expect(names).toContain("customer_tag");
48
+ });
49
+
50
+ test("an empty database reports zeroes rather than nulls", async () => {
51
+ expect(await loadDashboardStats(db)).toEqual({
52
+ customers: 0,
53
+ products: 0,
54
+ orders: 0,
55
+ draft: 0,
56
+ confirmed: 0,
57
+ delivered: 0,
58
+ revenueCents: 0,
59
+ committedCents: 0,
60
+ });
61
+ });
62
+
63
+ /**
64
+ * The app seeds this at startup, so it runs on every launch. Running twice has to leave the same
65
+ * database it left the first time, or a second launch doubles the catalogue.
66
+ */
67
+ test("reference data gives the app a catalogue and customers, and is idempotent", async () => {
68
+ await ensureReferenceData(db);
69
+ const first = await loadDashboardStats(db);
70
+
71
+ expect(first.products).toBeGreaterThan(0);
72
+ expect(first.customers).toBeGreaterThan(0);
73
+ // Reference data is what you need to write an order, not orders themselves.
74
+ expect(first.orders).toBe(0);
75
+
76
+ await ensureReferenceData(db);
77
+ expect(await loadDashboardStats(db)).toEqual(first);
78
+ });
79
+
80
+ test("seeding adds demo orders on top of the reference data", async () => {
81
+ await ensureReferenceData(db);
82
+ const before = await loadDashboardStats(db);
83
+
84
+ await seedSampleData(db, 4);
85
+ const stats = await loadDashboardStats(db);
86
+
87
+ expect(stats.customers).toBe(before.customers + 4);
88
+ expect(stats.products).toBe(before.products);
89
+ // Two orders per customer the seed adds.
90
+ expect(stats.orders).toBe(8);
91
+ expect(stats.revenueCents).toBeGreaterThan(0);
92
+ });
93
+
94
+ test("the order list joins the customer and totals its lines", async () => {
95
+ await seedSampleData(db, 2);
96
+ const orders = await searchOrders(db, "");
97
+
98
+ expect(orders.length).toBe(4);
99
+ const first = orders[0];
100
+ expect(first?.customerName).toMatch(/^Customer /);
101
+ expect(first?.city.length).toBeGreaterThan(0);
102
+ expect(first?.lines).toBeGreaterThan(0);
103
+ expect(first?.totalCents).toBeGreaterThan(0);
104
+ });
105
+
106
+ test("search filters on the customer name and the reference together", async () => {
107
+ await seedSampleData(db, 3);
108
+ const all = await searchOrders(db, "");
109
+
110
+ // Taken from the data rather than hard-coded: the reference customers are numbered first, so the
111
+ // ones the seed adds do not start at 1.
112
+ const name = all[0]?.customerName ?? "";
113
+ const byCustomer = await searchOrders(db, name);
114
+ expect(byCustomer.length).toBeGreaterThan(0);
115
+ expect(byCustomer.every((order) => order.customerName === name)).toBe(true);
116
+
117
+ const reference = all[0]?.reference ?? "";
118
+ const byReference = await searchOrders(db, reference);
119
+ expect(byReference.some((order) => order.reference === reference)).toBe(true);
120
+
121
+ expect(await searchOrders(db, "nothing-matches-this")).toEqual([]);
122
+ });
123
+
124
+ test("advancing a status cycles draft to confirmed to delivered and back", async () => {
125
+ await seedSampleData(db, 1);
126
+ const [order] = await searchOrders(db, "");
127
+ const id = order?.id ?? 0;
128
+
129
+ expect(order?.status).toBe("draft");
130
+
131
+ await advanceOrderStatus(db, id);
132
+ expect((await searchOrders(db, "")).find((row) => row.id === id)?.status).toBe("confirmed");
133
+
134
+ await advanceOrderStatus(db, id);
135
+ expect((await searchOrders(db, "")).find((row) => row.id === id)?.status).toBe("delivered");
136
+
137
+ await advanceOrderStatus(db, id);
138
+ expect((await searchOrders(db, "")).find((row) => row.id === id)?.status).toBe("draft");
139
+ });
140
+
141
+ test("advancing an order that does not exist is a no-op, not a throw", async () => {
142
+ await expect(advanceOrderStatus(db, 9999)).resolves.toBeUndefined();
143
+ });
144
+
145
+ test("customers come back ordered by name", async () => {
146
+ await seedSampleData(db, 3);
147
+ const customers = await listCustomers(db);
148
+ const names = customers.map((customer) => customer.name);
149
+
150
+ expect(names).toEqual([...names].sort());
151
+ });
152
+
153
+ test("clearing leaves every table empty", async () => {
154
+ await seedSampleData(db, 3);
155
+ await clearAll(db);
156
+
157
+ expect(await loadDashboardStats(db)).toEqual({
158
+ customers: 0,
159
+ products: 0,
160
+ orders: 0,
161
+ draft: 0,
162
+ confirmed: 0,
163
+ delivered: 0,
164
+ revenueCents: 0,
165
+ committedCents: 0,
166
+ });
167
+ expect(await searchOrders(db, "")).toEqual([]);
168
+ });
169
+
170
+ test("an order's total is quantity times unit price, summed over its lines", async () => {
171
+ await db
172
+ .insertInto("customer")
173
+ .values({ created_at: "now", name: "Solo", city: "Oran" })
174
+ .execute();
175
+ await db
176
+ .insertInto("product")
177
+ .values({ created_at: "now", name: "Widget", price_cents: 1000 })
178
+ .execute();
179
+
180
+ const customer = await db.selectFrom("customer").select("id").executeTakeFirstOrThrow();
181
+ const product = await db
182
+ .selectFrom("product")
183
+ .select(["id", "price_cents"])
184
+ .executeTakeFirstOrThrow();
185
+
186
+ // Two lines, quantities 1 and 2 at 1000 each: 1000 + 2000.
187
+ await createOrder(db, customer.id, [product, product]);
188
+
189
+ const [order] = await searchOrders(db, "");
190
+ expect(order?.lines).toBe(2);
191
+ expect(order?.totalCents).toBe(3000);
192
+ });
193
+
194
+ test("committed revenue counts only confirmed and delivered orders", async () => {
195
+ await seedSampleData(db, 2);
196
+ const before = await loadDashboardStats(db);
197
+
198
+ // Everything starts as a draft, so nothing is committed yet.
199
+ expect(before.draft).toBe(4);
200
+ expect(before.committedCents).toBe(0);
201
+ expect(before.revenueCents).toBeGreaterThan(0);
202
+
203
+ const [first] = await searchOrders(db, "");
204
+ await setOrderStatus(db, first?.id ?? 0, "confirmed");
205
+
206
+ const after = await loadDashboardStats(db);
207
+ expect(after.draft).toBe(3);
208
+ expect(after.confirmed).toBe(1);
209
+ expect(after.committedCents).toBe(first?.totalCents ?? -1);
210
+ });
211
+
212
+ test("the next reference counts up and survives a deletion without reuse", async () => {
213
+ expect(await nextOrderReference(db)).toBe("SO-0001");
214
+
215
+ const customer = await db
216
+ .insertInto("customer")
217
+ .values({ created_at: "now", name: "Solo", city: "Oran" })
218
+ .returning("id")
219
+ .executeTakeFirstOrThrow();
220
+ await db
221
+ .insertInto("product")
222
+ .values({ created_at: "now", name: "Widget", price_cents: 500 })
223
+ .execute();
224
+ const product = await db.selectFrom("product").select("id").executeTakeFirstOrThrow();
225
+
226
+ await saveOrder(db, {
227
+ customerId: customer.id,
228
+ reference: "SO-0001",
229
+ lines: [{ productId: product.id, quantity: 2, unitPriceCents: 500 }],
230
+ });
231
+ expect(await nextOrderReference(db)).toBe("SO-0002");
232
+
233
+ await saveOrder(db, {
234
+ customerId: customer.id,
235
+ reference: "SO-0002",
236
+ lines: [{ productId: product.id, quantity: 1, unitPriceCents: 500 }],
237
+ });
238
+
239
+ // Removing the highest must not hand the same reference out twice.
240
+ const orders = await searchOrders(db, "SO-0002");
241
+ await deleteOrder(db, orders[0]?.id ?? 0);
242
+ expect(await nextOrderReference(db)).toBe("SO-0002");
243
+ });
244
+
245
+ test("saving writes the order and all of its lines together", async () => {
246
+ const customer = await db
247
+ .insertInto("customer")
248
+ .values({ created_at: "now", name: "Solo", city: "Oran" })
249
+ .returning("id")
250
+ .executeTakeFirstOrThrow();
251
+ await db
252
+ .insertInto("product")
253
+ .values({ created_at: "now", name: "Widget", price_cents: 250 })
254
+ .execute();
255
+ const product = await db.selectFrom("product").select("id").executeTakeFirstOrThrow();
256
+
257
+ await saveOrder(db, {
258
+ customerId: customer.id,
259
+ reference: "SO-0007",
260
+ lines: [
261
+ { productId: product.id, quantity: 3, unitPriceCents: 250 },
262
+ { productId: product.id, quantity: 1, unitPriceCents: 250 },
263
+ ],
264
+ });
265
+
266
+ const [order] = await searchOrders(db, "SO-0007");
267
+ expect(order?.lines).toBe(2);
268
+ expect(order?.totalCents).toBe(1000);
269
+ expect(order?.status).toBe("draft");
270
+ });
271
+
272
+ test("deleting an order takes its lines with it", async () => {
273
+ await seedSampleData(db, 1);
274
+ const [order] = await searchOrders(db, "");
275
+ const id = order?.id ?? 0;
276
+
277
+ await deleteOrder(db, id);
278
+
279
+ expect((await searchOrders(db, "")).some((row) => row.id === id)).toBe(false);
280
+ const orphans = await db
281
+ .selectFrom("order_line")
282
+ .select("id")
283
+ .where("order_id", "=", id)
284
+ .execute();
285
+ expect(orphans).toEqual([]);
286
+ });
287
+
288
+ test("seeding twice adds customers without duplicating the catalogue or throwing", async () => {
289
+ await seedSampleData(db, 2);
290
+ const first = await loadDashboardStats(db);
291
+
292
+ // This threw "UNIQUE constraint failed: tag.label" before the seed became additive.
293
+ await seedSampleData(db, 2);
294
+ const second = await loadDashboardStats(db);
295
+
296
+ expect(second.customers).toBe(first.customers + 2);
297
+ expect(second.orders).toBe(first.orders + 4);
298
+ // The catalogue is fixed, so a second press must not double it.
299
+ expect(second.products).toBe(first.products);
300
+
301
+ const tags = await db.selectFrom("tag").select("label").execute();
302
+ expect(new Set(tags.map((tag) => tag.label)).size).toBe(tags.length);
303
+
304
+ const names = (await listCustomers(db)).map((customer) => customer.name);
305
+ expect(new Set(names).size).toBe(names.length);
306
+ });
307
+
308
+ test("the order detail reads its lines, product names and customer tags", async () => {
309
+ await seedSampleData(db, 1);
310
+ const [row] = await searchOrders(db, "");
311
+ const detail = await loadOrderDetail(db, row?.id ?? 0);
312
+
313
+ expect(detail?.reference).toBe(row?.reference);
314
+ expect(detail?.customerName).toBe(row?.customerName);
315
+ expect(detail?.lines.length).toBe(row?.lines);
316
+ expect(detail?.lines.every((line) => line.productName.length > 0)).toBe(true);
317
+ expect(detail?.totalCents).toBe(row?.totalCents);
318
+ // Seeding attaches one tag per customer.
319
+ expect(detail?.tags.length).toBeGreaterThan(0);
320
+
321
+ // Each line total is quantity times unit price.
322
+ for (const line of detail?.lines ?? []) {
323
+ expect(line.lineTotalCents).toBe(line.quantity * line.unitPriceCents);
324
+ }
325
+ });
326
+
327
+ test("the order detail is null for an id that does not exist", async () => {
328
+ expect(await loadOrderDetail(db, 4242)).toBeNull();
329
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "jsx": "preserve",
8
+ "strict": true,
9
+ "noUnusedLocals": true,
10
+ "noEmit": true,
11
+ "isolatedModules": true,
12
+ "esModuleInterop": true,
13
+ "resolveJsonModule": true,
14
+ "skipLibCheck": true,
15
+ "types": ["vite/client"],
16
+ "paths": {
17
+ "@/*": ["./src/*"]
18
+ }
19
+ },
20
+ "include": ["src/**/*.ts", "src/**/*.vue", "auto-imports.d.ts", "components.d.ts"]
21
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["node"],
5
+ /**
6
+ * vite-plus bundles its own Vite while the unplugin packages depend on Vite as a peer, so the
7
+ * plugin array is compared across two copies of the same types and TS gives up with
8
+ * "excessive stack depth". `vp check` type-checks this file with a single resolution, so the
9
+ * comparison never happens there - which is why the app project below leaves it out.
10
+ */
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["vite.config.ts"]
14
+ }
@@ -0,0 +1,95 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath, URL } from "node:url";
3
+ import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
4
+ import tailwindcss from "@tailwindcss/vite";
5
+ import vue from "@vitejs/plugin-vue";
6
+ import AutoImport from "unplugin-auto-import/vite";
7
+ import Icons from "unplugin-icons/vite";
8
+ import IconsResolver from "unplugin-icons/resolver";
9
+ import Components from "unplugin-vue-components/vite";
10
+ import { defineConfig } from "vite-plus";
11
+ import {
12
+ Framework7VueResolver,
13
+ getFramework7AutoImports,
14
+ } from "./src/shared/utils/resolvers/resolvers.js";
15
+
16
+ const SRC = fileURLToPath(new URL("./src", import.meta.url));
17
+
18
+ // Read once so Settings can show the real name and version without importing the manifest
19
+ // into the bundle.
20
+ const pkg = JSON.parse(
21
+ readFileSync(fileURLToPath(new URL("./package.json", import.meta.url)), "utf-8"),
22
+ ) as { name: string; version: string };
23
+
24
+ export default defineConfig({
25
+ define: {
26
+ __APP_NAME__: JSON.stringify("App"),
27
+ __APP_VERSION__: JSON.stringify(pkg.version),
28
+ },
29
+
30
+ plugins: [
31
+ vue({
32
+ template: {
33
+ compilerOptions: {
34
+ // jeep-sqlite is a custom element, not a Vue component.
35
+ isCustomElement: (tag) => tag === "jeep-sqlite" || tag.startsWith("swiper-"),
36
+ },
37
+ },
38
+ }),
39
+
40
+ tailwindcss(),
41
+
42
+ Icons({ autoInstall: true, compiler: "vue3" }),
43
+
44
+ VueI18nPlugin({
45
+ include: [fileURLToPath(new URL("./src/locales/**", import.meta.url))],
46
+ }),
47
+
48
+ /**
49
+ * Composition API, i18n, vueuse and the Framework7 helpers are available without an import
50
+ * line. `auto-imports.d.ts` and `components.d.ts` are generated on first run and are what makes
51
+ * the editor, `vue-tsc` and the build agree - they are artifacts, not files to hand-edit.
52
+ */
53
+ AutoImport({
54
+ include: [/\.[tj]sx?$/, /\.vue$/, /\.vue\?vue/],
55
+ imports: ["vue", "vue-i18n", "@vueuse/core", getFramework7AutoImports()],
56
+ dirs: [
57
+ "src/shared/composables/**",
58
+ "src/shared/utils/**",
59
+ "src/plugins/**",
60
+ "src/modules/**/composables/**",
61
+ ],
62
+ dts: "auto-imports.d.ts",
63
+ vueTemplate: true,
64
+ viteOptimizeDeps: true,
65
+ injectAtEnd: true,
66
+ dirsScanOptions: { types: true },
67
+ }),
68
+
69
+ /**
70
+ * `Framework7VueResolver` is why no screen imports an `f7-*` component and why the app never
71
+ * calls `registerComponents`: each component is pulled from `framework7-vue` exactly where it
72
+ * is used. Icons resolve through the same pass, so `<i-f7-house-fill />` needs no import.
73
+ */
74
+ Components({
75
+ dts: "components.d.ts",
76
+ dirs: ["src/shared/components/**", "src/modules/**/views/**", "src/modules/**/components/**"],
77
+ extensions: ["vue", "ts"],
78
+ deep: true,
79
+ resolvers: [
80
+ Framework7VueResolver(),
81
+ IconsResolver({
82
+ prefix: "i",
83
+ alias: { f7: "framework7", mt: "material-symbols" },
84
+ enabledCollections: ["framework7", "material-symbols", "lucide"],
85
+ }),
86
+ ],
87
+ }),
88
+ ],
89
+
90
+ resolve: { alias: { "@": SRC } },
91
+ server: { port: 5173 },
92
+ build: { target: "esnext" },
93
+ lint: { options: { typeAware: false } },
94
+ fmt: {},
95
+ });