apptify 0.2.0 → 0.5.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/README.md +95 -22
- package/dist/apptify.css +1 -1
- package/dist/apptify.js +4153 -2177
- package/dist/components/button.d.ts +1 -1
- package/dist/components/content.d.ts +73 -362
- package/dist/components/date-picker.d.ts +10 -0
- package/dist/components/forms.d.ts +77 -18
- package/dist/components/foundation.d.ts +2061 -111
- package/dist/components/gallery-scrollbar.d.ts +46 -0
- package/dist/components/mobile-image-viewer.d.ts +166 -0
- package/dist/components/navibar.d.ts +80 -0
- package/dist/components/overlays.d.ts +127 -158
- package/dist/components/tabs.d.ts +117 -0
- package/dist/core/context.d.ts +573 -9
- package/dist/core/desktop-image-enter.d.ts +6 -0
- package/dist/core/desktop-image-return.d.ts +1 -0
- package/dist/core/device.d.ts +5 -0
- package/dist/core/image-gallery-motion.d.ts +2 -0
- package/dist/core/image-geometry.d.ts +28 -0
- package/dist/core/image-motion.d.ts +18 -0
- package/dist/core/layers.d.ts +2 -0
- package/dist/core/motion.d.ts +2 -0
- package/dist/core/popup-motion.d.ts +12 -0
- package/dist/core/popup-placement.d.ts +31 -0
- package/dist/core/preview-navigation.d.ts +5 -0
- package/dist/index.d.ts +1214 -339
- package/docs/COMPONENTS.md +17 -15
- package/docs/PLAYGROUND-TEMPLATE.md +74 -0
- package/docs/VALIDATION.md +24 -0
- package/package.json +26 -7
- package/templates/playground/README.md +17 -0
- package/templates/playground/gitignore +3 -0
- package/templates/playground/index.html +26 -0
- package/templates/playground/package.json +26 -0
- package/templates/playground/playground/App.vue +73 -0
- package/templates/playground/playground/ComponentDemo.vue +234 -0
- package/templates/playground/playground/catalog.ts +80 -0
- package/templates/playground/playground/components/ComponentIndex.vue +62 -0
- package/templates/playground/playground/components/PageFooter.vue +3 -0
- package/templates/playground/playground/components/UserMenu.vue +132 -0
- package/templates/playground/playground/components/not-found-paper-scene.vue +210 -0
- package/templates/playground/playground/components/not-found-paper-story.ts +149 -0
- package/templates/playground/playground/env.d.ts +1 -0
- package/templates/playground/playground/main.ts +8 -0
- package/templates/playground/playground/router/index.ts +24 -0
- package/templates/playground/playground/style.css +228 -0
- package/templates/playground/playground/views/[...all].vue +68 -0
- package/templates/playground/playground/views/components.vue +126 -0
- package/templates/playground/playground/views/index.vue +75 -0
- package/templates/playground/playground/views/settings.vue +98 -0
- package/templates/playground/public/images/SOURCES.md +21 -0
- package/templates/playground/public/images/airpods-max-orange.jpg +0 -0
- package/templates/playground/public/images/architecture.jpg +0 -0
- package/templates/playground/public/images/city.jpg +0 -0
- package/templates/playground/public/images/coast.jpg +0 -0
- package/templates/playground/public/images/desert.jpg +0 -0
- package/templates/playground/public/images/food.jpg +0 -0
- package/templates/playground/public/images/forest.jpg +0 -0
- package/templates/playground/public/images/lake.jpg +0 -0
- package/templates/playground/public/images/mountains.jpg +0 -0
- package/templates/playground/public/images/snow.jpg +0 -0
- package/templates/playground/public/images/sunset.jpg +0 -0
- package/templates/playground/public/images/waterfall.jpg +0 -0
- package/templates/playground/source-snapshot.json +111 -0
- package/templates/playground/tsconfig.json +24 -0
- package/templates/playground/vite.config.ts +9 -0
- package/tooling/create-playground.mjs +35 -0
- package/tooling/prepare-package.mjs +35 -0
- package/tooling/prepare-playground.mjs +90 -0
- package/tooling/test-playground-package.mjs +188 -0
- package/tooling/vite.d.ts +5 -0
- package/tooling/vite.js +10 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Demo image sources
|
|
2
|
+
|
|
3
|
+
These images are preview fixtures, not part of the npm library output.
|
|
4
|
+
|
|
5
|
+
- `airpods-max-orange.jpg`: Apple official store CDN, https://store.storeimages.cdn-apple.com/1/as-images.apple.com/is/airpods-max-select-202409-orange?wid=800&hei=800&fmt=jpeg&qlt=85 . Product imagery belongs to Apple; replace with your own licensed assets for production distribution. This project is not affiliated with Apple.
|
|
6
|
+
- `lake.jpg`: Unsplash, https://images.unsplash.com/photo-1470770841072-f978cf4d019e?auto=format&fit=crop&w=1200&q=85 . Demo landscape photo, retrieved 2026-09-26.
|
|
7
|
+
|
|
8
|
+
Design references: https://www.apple.com/store/ and https://vuetifyjs.com/en/directives/ripple/ . No Apple fonts are bundled; system fonts are used.
|
|
9
|
+
|
|
10
|
+
Additional AppleImage demo photos, retrieved 2026-10-02:
|
|
11
|
+
|
|
12
|
+
- `mountains.jpg`: Unsplash, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1000&q=80 .
|
|
13
|
+
- `forest.jpg`: Unsplash, https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&w=800&h=1200&q=80 .
|
|
14
|
+
- `coast.jpg`: Unsplash, https://images.unsplash.com/photo-1473116763249-2faaef81ccda?auto=format&fit=crop&w=1600&h=600&q=80 .
|
|
15
|
+
- `desert.jpg`: Unsplash, https://images.unsplash.com/photo-1509316785289-025f5b846b35?auto=format&fit=crop&w=1000&h=750&q=80 .
|
|
16
|
+
- `waterfall.jpg`: Unsplash, https://images.unsplash.com/photo-1432405972618-c60b0225b8f9?auto=format&fit=crop&w=700&h=1400&q=80 .
|
|
17
|
+
- `sunset.jpg`: Unsplash, https://images.unsplash.com/photo-1472120435266-53107fd0c44a?auto=format&fit=crop&w=1200&h=500&q=80 .
|
|
18
|
+
- `snow.jpg`: Unsplash, https://images.unsplash.com/photo-1418985991508-e47386d96a71?auto=format&fit=crop&w=900&h=1100&q=80 .
|
|
19
|
+
- `city.jpg`: Unsplash, https://images.unsplash.com/photo-1480714378408-67cf0d13bc1b?auto=format&fit=crop&w=800&h=1000&q=80 .
|
|
20
|
+
- `architecture.jpg`: Unsplash, https://images.unsplash.com/photo-1511818966892-d7d671e672a2?auto=format&fit=crop&w=700&h=1200&q=80 .
|
|
21
|
+
- `food.jpg`: Unsplash, https://images.unsplash.com/photo-1504674900247-0877df9cc836?auto=format&fit=crop&w=1000&h=800&q=80 .
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-10-03T06:52:26.901Z",
|
|
3
|
+
"libraryVersion": "0.5.0",
|
|
4
|
+
"files": {
|
|
5
|
+
"index.html": "75f6d93e3962ddca97a13b666a04d98ba9b263b6c288171383db764215034fea",
|
|
6
|
+
"playground/App.vue": "ef3f06f80758a3d4198ee90eb05f766a89b6156b4899fd566f8dca9b42e0ffc9",
|
|
7
|
+
"playground/ComponentDemo.vue": "0801a9eba5aa2e06f3bbd972b25647fed145ee6d630fd4d183eb016862f25ad4",
|
|
8
|
+
"playground/catalog.ts": "80b10ca45b18a611baec19361c70a967da655690c04091d23541192cd02fcecd",
|
|
9
|
+
"playground/components/ComponentIndex.vue": "2223f76786ef3dd1658fbb1ce29246e9bd155343d2e9bea9cb0674bc4a4ef879",
|
|
10
|
+
"playground/components/PageFooter.vue": "29c3ae396450c961aa13f3edc7d6b15f9dd12e95033242328fbe2093fb5ff0eb",
|
|
11
|
+
"playground/components/UserMenu.vue": "e584cc4c97c8e5a582be259f5aafc0ac355f4339133f6f572f079d979aa96c10",
|
|
12
|
+
"playground/components/not-found-paper-scene.vue": "39f12c23f04d72131d9da51e9a960894b621f2581c25e5b18463f7b458127209",
|
|
13
|
+
"playground/components/not-found-paper-story.ts": "b7c02528ce58080662f0e55bc4a6b11c170fc987b3b5e377ff7c0db8f77ddf10",
|
|
14
|
+
"playground/env.d.ts": "daae3800510e936d8308b7a9ebb0cb28d8f3fcd449e41386feea05d29bd12540",
|
|
15
|
+
"playground/main.ts": "efa30fb81f5c5f46ca21dc32a96fb08b32665b2d7de9d426e52c617662203285",
|
|
16
|
+
"playground/router/index.ts": "fe5f1c4b53f2f636bcb25727013b5cfb1e96bcadfd7f1a841537583fc98eccf5",
|
|
17
|
+
"playground/style.css": "940d33a07191f6143b1aef319bf7d09db479d2c0b3b3f6743a4362f8ea9c9e8b",
|
|
18
|
+
"playground/views/[...all].vue": "6d1cd2b7a67cc9e2b2935555633cda148dd81b5ca441bcb9b43029b8f719d3f9",
|
|
19
|
+
"playground/views/components.vue": "5edb34d2100f192d18fb7789a188b880c26daccad9597fe68d319927ac14e15a",
|
|
20
|
+
"playground/views/index.vue": "703fc1a39c31aaff17c8f6c69f418d455b9a16af5bb1b977403d77b09ef3f1e4",
|
|
21
|
+
"playground/views/settings.vue": "43e1074d8a17104ad88f72e7c7787a390b76e7948fa321b1c667e7ba41c9cae6",
|
|
22
|
+
"public/images/SOURCES.md": "24ccb6625b1168e48550b7651e9fc20cb747324d87666d39199608c7db20541b",
|
|
23
|
+
"public/images/airpods-max-orange.jpg": "f365fe9e374e2cede88f3e68487f6a773a767873978bcf20e4b21dee08aca966",
|
|
24
|
+
"public/images/architecture.jpg": "928ea321954f0edabc1eed3c908c6b1fc93b247badcbd89d8dc5ba237420c26b",
|
|
25
|
+
"public/images/city.jpg": "2ff983f15a2116a169e0a77e841e546848951185d0be4cf1ba8983c350c05109",
|
|
26
|
+
"public/images/coast.jpg": "20fe6b79672be065307c3eed01d6a067969489c79baec35020a14a8f4cdecb3e",
|
|
27
|
+
"public/images/desert.jpg": "f2bcb6ee381cae278a29e89c3e1ea066bd34e885a0d3c08e856f994156cb546e",
|
|
28
|
+
"public/images/food.jpg": "9a36b1dacff2b9e7a1ebcdb14be714c17ee2d15d61de043faf178d5473753553",
|
|
29
|
+
"public/images/forest.jpg": "f57adf0b41247970af93cb4897fdfdf4bfe4f2004319ff905c912a419a43a1f6",
|
|
30
|
+
"public/images/lake.jpg": "26f1167b6c07e4d67cd90c469390ebb310bfa082a7d86e0e3758f616176378c7",
|
|
31
|
+
"public/images/mountains.jpg": "57aae81c2886f3404c582dd637ed325559616621466c027d2d1039bd918a213a",
|
|
32
|
+
"public/images/snow.jpg": "59fec6d2bbf9df11b7c697c57a253b7bd4b5a097489ce1017fb5209b9ee54b8e",
|
|
33
|
+
"public/images/sunset.jpg": "4ee91e88f1e341143a807ef48fdfc24d63aa045d99351b4ea8ad7efcfef6ef9b",
|
|
34
|
+
"public/images/waterfall.jpg": "cfd55ff5f41d3a58e91793d0e26a9ca90c86760aa401da033fbfa77d3364f238"
|
|
35
|
+
},
|
|
36
|
+
"buildSources": {
|
|
37
|
+
"src/components/button.ts": "45db9f1488643ea03e6af5a090d6811fd815993f6ad5a5801cd9227394224439",
|
|
38
|
+
"src/components/content.ts": "e2206c834004ee7e099b2b9b4d7afe14b55a18c89569291ce44bc9a472d70f98",
|
|
39
|
+
"src/components/date-picker.ts": "130c127d4ab736e633b7eeaea5e9aa8937783391c690fe10479d90256b6930bd",
|
|
40
|
+
"src/components/forms.ts": "18cc26a711283a221c712e3c68791b5d1943240b954ca47b0a8525ae0c8598c5",
|
|
41
|
+
"src/components/foundation.ts": "671c6aa20cd2a253483f26d290a0080dba43c2656ae470dd5a84755a729bd1a4",
|
|
42
|
+
"src/components/gallery-scrollbar.ts": "3763c0f05629fed9d8b82514339f768d305120b56308f8ae8a9460c9853719bb",
|
|
43
|
+
"src/components/mobile-image-viewer.ts": "66cecdf485a731c231823d1137cffc26c92cde45d94ef8def5b0bff070de48f4",
|
|
44
|
+
"src/components/motion.ts": "d294cc879e690e07358765e33e318cd4f427b17ac66cb5f1bf5848c9fc453419",
|
|
45
|
+
"src/components/navibar.ts": "01c58cc6563273006ae91c43853283f60499de9fc88ba3a072ca64ef07f7eb2c",
|
|
46
|
+
"src/components/overlays.ts": "840bb7b1620d05beae767a2d6af9493f4e0dade8c098517d4260ca644221f3ba",
|
|
47
|
+
"src/components/tabs.ts": "a80fdf41efff49c2870859d6c0a97e5bbba69055706309acd5b53eafe8c02f9d",
|
|
48
|
+
"src/core/context.ts": "c96ccc6e75ac53b9461730dd61dd79952ca23004f08b1f9d199ca6478034fe4e",
|
|
49
|
+
"src/core/desktop-image-enter.ts": "6852f1a0a5d6cccfbe50509934729fea9e82ccd4c0cf9bdeda629eed2d9e3ef0",
|
|
50
|
+
"src/core/desktop-image-return.ts": "cfb4e9fc6cea3cd8dd614b940345e5314b6e74f4748c6066ab1cc6b4c535fe08",
|
|
51
|
+
"src/core/device.ts": "da97e64aa1a041d88c77818a836cee0e2401fb73a532bc173c2e19fa471ca97f",
|
|
52
|
+
"src/core/image-gallery-motion.ts": "0eab74e68687a930fa2501e7d23a63e83f0124bc5d9da55be541817f40ee2a33",
|
|
53
|
+
"src/core/image-geometry.ts": "6eb4d4e2a2afd078ccce010ec8aae32d89bf70a1e8c2c522b5e81040a97a1037",
|
|
54
|
+
"src/core/image-motion.ts": "da1eded52a297332d314c9c1e27dc34ce031e07b6acce147ab2ae6eb9d7895d5",
|
|
55
|
+
"src/core/layers.ts": "fe15fa50c82155e894f2e42a80340bd61bda2cfa4d3e9646a69f556c49fc5b04",
|
|
56
|
+
"src/core/motion.ts": "cb8b0cce9f4343cfc927466e21c3a315cf99df1e23380bfa10937b4caea62514",
|
|
57
|
+
"src/core/popup-motion.ts": "528f3a0d95eea5e63c4c2732b247eb71824c9fafc4d3a822b03d180a29902924",
|
|
58
|
+
"src/core/popup-placement.ts": "605138db77d9e1ebb9433281533ea607d9cead7689c6ea2596a9b20a277251d3",
|
|
59
|
+
"src/core/preview-navigation.ts": "8de5cc0f62b16b0590f212de6f7bb234859ec483a612673a87dd8fe9e260a813",
|
|
60
|
+
"src/index.ts": "75e2a540a1310aeda2e2eb64ffd8e1c93f83f2fab915f8f73432c4f554a67d55",
|
|
61
|
+
"src/styles/base.css": "02973e340575383182b378daabdbafcd7bc1e1191aea829ba11dc41010854547",
|
|
62
|
+
"src/styles/content.css": "3a57b7f24f5edb0245e8edba463f4a4fd5bd46fbe515d3332ba4eb708c4fb6f0",
|
|
63
|
+
"src/styles/date-picker.css": "a64396f52b95df206bca5c5a6f5d68777bc0b9426d08664998be23694727b2d7",
|
|
64
|
+
"src/styles/forms.css": "156dc5e5d43020abf3ca56f1d0b9898d4bf57e4a00588f6a764f69939b66b57b",
|
|
65
|
+
"src/styles/navibar.css": "56bf34300e3064e42cb0a0d35a2d6d4cc7ff5ba69740a6d1d6af55397fc555d6",
|
|
66
|
+
"src/styles/overlays.css": "7b975aa8b6796a9b2e051555807e834a399430397b8b8395c0b6b35af034d0ef",
|
|
67
|
+
"src/styles/ripple.css": "df0a5f661a862b5232bac256958f4bae6f555dd1f4a9c1432f61e85de71bdfea",
|
|
68
|
+
"playground/App.vue": "ef3f06f80758a3d4198ee90eb05f766a89b6156b4899fd566f8dca9b42e0ffc9",
|
|
69
|
+
"playground/ComponentDemo.vue": "0801a9eba5aa2e06f3bbd972b25647fed145ee6d630fd4d183eb016862f25ad4",
|
|
70
|
+
"playground/catalog.ts": "80b10ca45b18a611baec19361c70a967da655690c04091d23541192cd02fcecd",
|
|
71
|
+
"playground/components/ComponentIndex.vue": "2223f76786ef3dd1658fbb1ce29246e9bd155343d2e9bea9cb0674bc4a4ef879",
|
|
72
|
+
"playground/components/PageFooter.vue": "29c3ae396450c961aa13f3edc7d6b15f9dd12e95033242328fbe2093fb5ff0eb",
|
|
73
|
+
"playground/components/UserMenu.vue": "e584cc4c97c8e5a582be259f5aafc0ac355f4339133f6f572f079d979aa96c10",
|
|
74
|
+
"playground/components/not-found-paper-scene.vue": "39f12c23f04d72131d9da51e9a960894b621f2581c25e5b18463f7b458127209",
|
|
75
|
+
"playground/components/not-found-paper-story.ts": "b7c02528ce58080662f0e55bc4a6b11c170fc987b3b5e377ff7c0db8f77ddf10",
|
|
76
|
+
"playground/env.d.ts": "daae3800510e936d8308b7a9ebb0cb28d8f3fcd449e41386feea05d29bd12540",
|
|
77
|
+
"playground/main.ts": "efa30fb81f5c5f46ca21dc32a96fb08b32665b2d7de9d426e52c617662203285",
|
|
78
|
+
"playground/router/index.ts": "fe5f1c4b53f2f636bcb25727013b5cfb1e96bcadfd7f1a841537583fc98eccf5",
|
|
79
|
+
"playground/style.css": "940d33a07191f6143b1aef319bf7d09db479d2c0b3b3f6743a4362f8ea9c9e8b",
|
|
80
|
+
"playground/views/[...all].vue": "6d1cd2b7a67cc9e2b2935555633cda148dd81b5ca441bcb9b43029b8f719d3f9",
|
|
81
|
+
"playground/views/components.vue": "5edb34d2100f192d18fb7789a188b880c26daccad9597fe68d319927ac14e15a",
|
|
82
|
+
"playground/views/index.vue": "703fc1a39c31aaff17c8f6c69f418d455b9a16af5bb1b977403d77b09ef3f1e4",
|
|
83
|
+
"playground/views/settings.vue": "43e1074d8a17104ad88f72e7c7787a390b76e7948fa321b1c667e7ba41c9cae6",
|
|
84
|
+
"public/images/SOURCES.md": "24ccb6625b1168e48550b7651e9fc20cb747324d87666d39199608c7db20541b",
|
|
85
|
+
"public/images/airpods-max-orange.jpg": "f365fe9e374e2cede88f3e68487f6a773a767873978bcf20e4b21dee08aca966",
|
|
86
|
+
"public/images/architecture.jpg": "928ea321954f0edabc1eed3c908c6b1fc93b247badcbd89d8dc5ba237420c26b",
|
|
87
|
+
"public/images/city.jpg": "2ff983f15a2116a169e0a77e841e546848951185d0be4cf1ba8983c350c05109",
|
|
88
|
+
"public/images/coast.jpg": "20fe6b79672be065307c3eed01d6a067969489c79baec35020a14a8f4cdecb3e",
|
|
89
|
+
"public/images/desert.jpg": "f2bcb6ee381cae278a29e89c3e1ea066bd34e885a0d3c08e856f994156cb546e",
|
|
90
|
+
"public/images/food.jpg": "9a36b1dacff2b9e7a1ebcdb14be714c17ee2d15d61de043faf178d5473753553",
|
|
91
|
+
"public/images/forest.jpg": "f57adf0b41247970af93cb4897fdfdf4bfe4f2004319ff905c912a419a43a1f6",
|
|
92
|
+
"public/images/lake.jpg": "26f1167b6c07e4d67cd90c469390ebb310bfa082a7d86e0e3758f616176378c7",
|
|
93
|
+
"public/images/mountains.jpg": "57aae81c2886f3404c582dd637ed325559616621466c027d2d1039bd918a213a",
|
|
94
|
+
"public/images/snow.jpg": "59fec6d2bbf9df11b7c697c57a253b7bd4b5a097489ce1017fb5209b9ee54b8e",
|
|
95
|
+
"public/images/sunset.jpg": "4ee91e88f1e341143a807ef48fdfc24d63aa045d99351b4ea8ad7efcfef6ef9b",
|
|
96
|
+
"public/images/waterfall.jpg": "cfd55ff5f41d3a58e91793d0e26a9ca90c86760aa401da033fbfa77d3364f238",
|
|
97
|
+
"docs/COMPONENTS.md": "ca4a01cefaddd3e45a1cfdaef3bc0474fa6a831de9802fcd4c27e0926da00a70",
|
|
98
|
+
"docs/PLAYGROUND-TEMPLATE.md": "b8a418d940cb99c53dd7f95c4f44da2682dbaf03201ec7eccfe7234b198f1408",
|
|
99
|
+
"docs/VALIDATION.md": "fb73cf9a3ae78c87093c89627ad6fdd9edc516b87aefd7244849d731283a1d0f",
|
|
100
|
+
"README.md": "0a4838e51c0d6bd2269411176ad4dbb89293ec77c332e9ed95b0f7f66f07ca9f",
|
|
101
|
+
"package.json": "856dd5ba89db5b5ebe685f711f1c1851eb4869ffea94f860b5918325428c0f7f",
|
|
102
|
+
"index.html": "75f6d93e3962ddca97a13b666a04d98ba9b263b6c288171383db764215034fea",
|
|
103
|
+
"vite.config.ts": "df2eae92f81c9b9007549de5721a2311dd9b918dea45a4c7b8b4979f247e49ae",
|
|
104
|
+
"vite.lib.config.ts": "f877c1772da70c9fed5174118ca36a0fc25fd46cce49b71280a5f91c4f8003a3",
|
|
105
|
+
"tsconfig.json": "09590870bfbf6e90614d8a34af38b9a72902e8747ecd1a76be47ab24c842f9ea",
|
|
106
|
+
"tsconfig.lib.json": "c885806b003cee01192cddcaf32df3e7c6d369e645360398aa088c14b1c4792f",
|
|
107
|
+
"tooling/create-playground.mjs": "c45480c5982a64efd06f69dfb4620c65fd8ea35d01877b8574e7e2b30c095606",
|
|
108
|
+
"tooling/prepare-playground.mjs": "67e62e7e8bfd9a97f567474fe14e3776ce68166c97150c8d72509ba18efe4126",
|
|
109
|
+
"tooling/prepare-package.mjs": "cf57a112eec798412e2a68089d1b2d25b984600d6ffef99397ec253abf2fca57"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"jsx": "preserve",
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"lib": [
|
|
11
|
+
"ES2022",
|
|
12
|
+
"DOM",
|
|
13
|
+
"DOM.Iterable"
|
|
14
|
+
],
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"types": [
|
|
17
|
+
"vite/client"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"include": [
|
|
21
|
+
"playground/**/*.ts",
|
|
22
|
+
"playground/**/*.vue"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
import apptifyRoutes from 'apptify/vite'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [apptifyRoutes({ dirs: [{ dir: 'playground/views', baseRoute: '' }] }), vue()],
|
|
7
|
+
server: { host: '127.0.0.1' },
|
|
8
|
+
build: { outDir: 'dist' },
|
|
9
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cp, mkdir, readFile, readdir, rename, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { resolve, join, sep } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { execFileSync } from 'node:child_process'
|
|
6
|
+
|
|
7
|
+
const root = fileURLToPath(new URL('../', import.meta.url))
|
|
8
|
+
const destination = process.argv[2]
|
|
9
|
+
if (!destination || ['--help', '-h'].includes(destination)) {
|
|
10
|
+
console.log('Usage: apptify-playground <empty-directory>\nCopies the complete editable playground and a portable local library archive.\nThen run npm install and npm run dev in that directory.')
|
|
11
|
+
process.exit(destination ? 0 : 1)
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const target = resolve(destination)
|
|
15
|
+
const packageRoot = resolve(root)
|
|
16
|
+
if (target === packageRoot || target.startsWith(packageRoot + sep)) throw new Error('Destination must be outside the installed apptify package')
|
|
17
|
+
const existing = await readdir(target).catch(error => { if (error.code === 'ENOENT') return []; throw error })
|
|
18
|
+
if (existing.length) throw new Error('Destination must be empty: ' + target)
|
|
19
|
+
const template = join(root, 'templates/playground')
|
|
20
|
+
await readFile(join(template, 'package.json'))
|
|
21
|
+
await mkdir(target, { recursive: true })
|
|
22
|
+
await cp(template, target, { recursive: true })
|
|
23
|
+
await rename(join(target, 'gitignore'), join(target, '.gitignore'))
|
|
24
|
+
const vendor = join(target, 'vendor')
|
|
25
|
+
await mkdir(vendor)
|
|
26
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
27
|
+
const packed = JSON.parse(execFileSync(npm, ['pack', '--ignore-scripts', '--json', '--loglevel=error', '--pack-destination', vendor], { cwd: root, encoding: 'utf8' }))
|
|
28
|
+
const manifest = JSON.parse(await readFile(join(target, 'package.json'), 'utf8'))
|
|
29
|
+
manifest.dependencies.apptify = 'file:./vendor/' + packed[0].filename
|
|
30
|
+
await writeFile(join(target, 'package.json'), JSON.stringify(manifest, null, 2) + '\n')
|
|
31
|
+
console.log('Created ' + target + '\nNext: cd ' + JSON.stringify(target) + ' && npm install && npm run dev')
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(error.message)
|
|
34
|
+
process.exitCode = 1
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { readFile, readdir, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { join, relative } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { createHash } from 'node:crypto'
|
|
6
|
+
import { execFileSync } from 'node:child_process'
|
|
7
|
+
|
|
8
|
+
const root = fileURLToPath(new URL('../', import.meta.url))
|
|
9
|
+
async function sourceHashes() {
|
|
10
|
+
const files = {}
|
|
11
|
+
const record = async path => {
|
|
12
|
+
files[relative(root, path).replaceAll('\\', '/')] = createHash('sha256').update(await readFile(path)).digest('hex')
|
|
13
|
+
}
|
|
14
|
+
const directory = async path => {
|
|
15
|
+
for (const entry of await readdir(path, { withFileTypes: true })) {
|
|
16
|
+
if (entry.name === '.DS_Store') continue
|
|
17
|
+
const child = join(path, entry.name)
|
|
18
|
+
if (entry.isDirectory()) await directory(child)
|
|
19
|
+
else await record(child)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (const name of ['src', 'playground', 'public', 'docs']) await directory(join(root, name))
|
|
23
|
+
for (const name of ['README.md', 'package.json', 'index.html', 'vite.config.ts', 'vite.lib.config.ts', 'tsconfig.json', 'tsconfig.lib.json', 'tooling/create-playground.mjs', 'tooling/prepare-playground.mjs', 'tooling/prepare-package.mjs']) await record(join(root, name))
|
|
24
|
+
return files
|
|
25
|
+
}
|
|
26
|
+
const before = await sourceHashes()
|
|
27
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
28
|
+
execFileSync(npm, ['run', 'build'], { cwd: root, stdio: 'inherit' })
|
|
29
|
+
execFileSync(process.execPath, [join(root, 'tooling/prepare-playground.mjs')], { cwd: root, stdio: 'inherit' })
|
|
30
|
+
assert.deepEqual(await sourceHashes(), before, 'Sources changed during the build; rerun packing to take the latest snapshot. No source file was restored.')
|
|
31
|
+
const snapshotPath = join(root, 'templates/playground/source-snapshot.json')
|
|
32
|
+
const snapshot = JSON.parse(await readFile(snapshotPath, 'utf8'))
|
|
33
|
+
snapshot.buildSources = before
|
|
34
|
+
await writeFile(snapshotPath, JSON.stringify(snapshot, null, 2) + '\n')
|
|
35
|
+
console.log('Library build and editable template share the same source snapshot.')
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { basename, join, relative } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
|
|
6
|
+
const root = fileURLToPath(new URL('../', import.meta.url))
|
|
7
|
+
const output = join(root, 'templates/playground')
|
|
8
|
+
const library = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'))
|
|
9
|
+
await rm(output, { recursive: true, force: true })
|
|
10
|
+
await mkdir(output, { recursive: true })
|
|
11
|
+
const copyOptions = { recursive: true, filter: source => basename(source) !== '.DS_Store' }
|
|
12
|
+
await cp(join(root, 'playground'), join(output, 'playground'), copyOptions)
|
|
13
|
+
await cp(join(root, 'public'), join(output, 'public'), copyOptions)
|
|
14
|
+
await cp(join(root, 'index.html'), join(output, 'index.html'))
|
|
15
|
+
|
|
16
|
+
const snapshot = { createdAt: new Date().toISOString(), libraryVersion: library.version, files: {} }
|
|
17
|
+
async function record(directory) {
|
|
18
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
19
|
+
const path = join(directory, entry.name)
|
|
20
|
+
if (entry.isDirectory()) await record(path)
|
|
21
|
+
else snapshot.files[relative(output, path)] = createHash('sha256').update(await readFile(path)).digest('hex')
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
await record(output)
|
|
25
|
+
await writeFile(join(output, 'source-snapshot.json'), JSON.stringify(snapshot, null, 2) + '\n')
|
|
26
|
+
|
|
27
|
+
async function adapt(directory) {
|
|
28
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
29
|
+
const path = join(directory, entry.name)
|
|
30
|
+
if (entry.isDirectory()) await adapt(path)
|
|
31
|
+
else if (/\.(vue|ts)$/.test(entry.name)) {
|
|
32
|
+
let source = await readFile(path, 'utf8')
|
|
33
|
+
source = source.replace(/(['"])(?:\.\.\/)+src(?:\/(?:index|core\/motion|core\/context|components\/content))?\1/g, "'apptify'")
|
|
34
|
+
if (/(['"])(?:\.\.\/)+src(?:\/[^'"]*)?\1/.test(source)) throw new Error('Unmapped private library import: ' + path)
|
|
35
|
+
if (entry.name === 'main.ts') source = "import 'apptify/style.css'\n" + source
|
|
36
|
+
if (path === join(output, 'playground/App.vue')) {
|
|
37
|
+
const navigationEnd = ']\n\nfunction navigate'
|
|
38
|
+
if (!source.includes(navigationEnd)) throw new Error('App navigation layout changed; update template adaptation')
|
|
39
|
+
source = source.replace('to="/" aria-label="Apptify 首页"', ':to="homePath" aria-label="Apptify 首页"')
|
|
40
|
+
source = source.replace(navigationEnd, "].filter(item => router.getRoutes().some(page => page.path === item.href))\nconst homePath = navigation[0]?.href ?? '/'\n\nfunction navigate")
|
|
41
|
+
}
|
|
42
|
+
if (path === join(output, 'playground/views/[...all].vue')) {
|
|
43
|
+
source = source.replace('to="/" custom', ':to="homePath" custom')
|
|
44
|
+
source = source.replace('const router = useRouter()', "const router = useRouter()\nconst homePath = router.getRoutes().some(page => page.path === '/') ? '/' : '/settings'")
|
|
45
|
+
source = source.replace("else void router.push('/')", 'else void router.push(homePath)')
|
|
46
|
+
}
|
|
47
|
+
await writeFile(path, source)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await adapt(join(output, 'playground'))
|
|
52
|
+
const manifest = {
|
|
53
|
+
name: 'apptify-app', version: '0.0.0', private: true, type: 'module',
|
|
54
|
+
scripts: { dev: 'vite', typecheck: 'vue-tsc --noEmit', build: 'vue-tsc --noEmit && vite build', preview: 'vite preview --host 127.0.0.1' },
|
|
55
|
+
dependencies: {
|
|
56
|
+
apptify: '^' + library.version, vue: library.peerDependencies.vue,
|
|
57
|
+
'vue-router': library.devDependencies['vue-router'], vuetify: library.peerDependencies.vuetify,
|
|
58
|
+
'lucide-vue-next': library.dependencies['lucide-vue-next'],
|
|
59
|
+
},
|
|
60
|
+
devDependencies: Object.fromEntries(['@vitejs/plugin-vue', 'typescript', 'vite', 'vite-plugin-pages', 'vue-tsc'].map(name => [name, library.devDependencies[name]])),
|
|
61
|
+
}
|
|
62
|
+
await writeFile(join(output, 'package.json'), JSON.stringify(manifest, null, 2) + '\n')
|
|
63
|
+
const tsconfig = JSON.parse(await readFile(join(root, 'tsconfig.json'), 'utf8'))
|
|
64
|
+
tsconfig.include = ['playground/**/*.ts', 'playground/**/*.vue']
|
|
65
|
+
await writeFile(join(output, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2) + '\n')
|
|
66
|
+
await writeFile(join(output, 'vite.config.ts'), [
|
|
67
|
+
"import { defineConfig } from 'vite'",
|
|
68
|
+
"import vue from '@vitejs/plugin-vue'",
|
|
69
|
+
"import apptifyRoutes from 'apptify/vite'",
|
|
70
|
+
'',
|
|
71
|
+
'export default defineConfig({',
|
|
72
|
+
" plugins: [apptifyRoutes({ dirs: [{ dir: 'playground/views', baseRoute: '' }] }), vue()],",
|
|
73
|
+
" server: { host: '127.0.0.1' },",
|
|
74
|
+
" build: { outDir: 'dist' },",
|
|
75
|
+
'})', '',
|
|
76
|
+
].join('\n'))
|
|
77
|
+
// npm excludes .gitignore from tarballs; the copy command restores its dot.
|
|
78
|
+
await writeFile(join(output, 'gitignore'), 'node_modules/\ndist/\n.env.local\n')
|
|
79
|
+
await writeFile(join(output, 'README.md'), [
|
|
80
|
+
'# Apptify 可编辑应用模板', '',
|
|
81
|
+
'完整源码位于 playground/,静态图片与来源说明位于 public/images/。导航、账号/登录、设置、404 和页面动效均可直接编辑复用。', '',
|
|
82
|
+
'通过 apptify-playground 复制命令创建时,vendor/ 包含本地组件库 tgz,package.json 使用相对 file: 依赖;移动整个项目后仍可独立安装。', '',
|
|
83
|
+
'运行 npm install、npm run typecheck、npm run build;开发使用 npm run dev。请使用 Node.js 22 或 24 LTS。', '',
|
|
84
|
+
'开始业务开发可直接删除 playground/views/index.vue 和 playground/views/components.vue,再添加自己的页面;文件路由自动生成,导航自动隐藏已删除页面,品牌链接回到第一个保留的导航页面。删除页面后重启开发服务。', '',
|
|
85
|
+
'导航项在 playground/App.vue 的 navigation 数组;页面文件在 playground/views/;布局和账号入口在 App.vue 与 playground/components/UserMenu.vue。', '',
|
|
86
|
+
'设置保留主题、动效与玻璃偏好。404 在 playground/views/[...all].vue;首页和组件总览没有被其他页面硬导入。ComponentDemo.vue、catalog.ts 和 ComponentIndex.vue 可以保留,也可在删除总览页后按需删除。', '',
|
|
87
|
+
'登录、注册、忘记密码、邮箱验证码仍为 UI 演示入口,需要接入自己的后端。部署采用 history 路由,服务器应把未知页面路径回退到 index.html。', '',
|
|
88
|
+
'若手工复制包内 templates/playground,先将 package.json 的 apptify 依赖替换为实际本地 tgz 路径,或安装自己的正式发布版本;当前项目尚未发布 npm。', '',
|
|
89
|
+
].join('\n'))
|
|
90
|
+
console.log('Prepared editable playground template: ' + output)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { join, resolve } from 'node:path'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { createHash } from 'node:crypto'
|
|
6
|
+
import { execFileSync, spawn } from 'node:child_process'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { chromium } from '@playwright/test'
|
|
9
|
+
|
|
10
|
+
const root = fileURLToPath(new URL('../', import.meta.url))
|
|
11
|
+
const scratch = await mkdtemp(join(tmpdir(), 'apptify-package-'))
|
|
12
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
13
|
+
const run = (args, cwd = root) => execFileSync(npm, args, { cwd, stdio: 'inherit', timeout: 240_000 })
|
|
14
|
+
console.log('Package acceptance workspace: ' + scratch)
|
|
15
|
+
const archiveInput = process.argv[2] ? resolve(process.argv[2]) : undefined
|
|
16
|
+
const archiveSha256 = archiveInput ? createHash('sha256').update(await readFile(archiveInput)).digest('hex') : undefined
|
|
17
|
+
const packArgs = ['pack', '--ignore-scripts', '--json', '--loglevel=error', '--pack-destination', scratch]
|
|
18
|
+
if (archiveInput) packArgs.push(archiveInput)
|
|
19
|
+
const packed = JSON.parse(execFileSync(npm, packArgs, { cwd: root, encoding: 'utf8' }))[0]
|
|
20
|
+
const contents = new Set(packed.files.map(file => file.path))
|
|
21
|
+
const snapshot = archiveInput
|
|
22
|
+
? JSON.parse(execFileSync('tar', ['-xOf', join(scratch, packed.filename), 'package/templates/playground/source-snapshot.json'], { encoding: 'utf8' }))
|
|
23
|
+
: JSON.parse(await readFile(join(root, 'templates/playground/source-snapshot.json'), 'utf8'))
|
|
24
|
+
for (const path of Object.keys(snapshot.files)) assert(contents.has('templates/playground/' + path), 'Missing template file: ' + path)
|
|
25
|
+
for (const path of ['package.json', 'vite.config.ts', 'tsconfig.json', 'README.md', 'gitignore', 'source-snapshot.json']) {
|
|
26
|
+
assert(contents.has('templates/playground/' + path), 'Missing template configuration: ' + path)
|
|
27
|
+
}
|
|
28
|
+
assert(contents.has('tooling/create-playground.mjs'))
|
|
29
|
+
assert(contents.has('dist/apptify.js'))
|
|
30
|
+
assert(contents.has('dist/apptify.css'))
|
|
31
|
+
console.log('Pack manifest includes every playground source and public resource (' + Object.keys(snapshot.files).length + ' files).')
|
|
32
|
+
|
|
33
|
+
const installer = join(scratch, 'installer')
|
|
34
|
+
await mkdir(installer)
|
|
35
|
+
await writeFile(join(installer, 'package.json'), '{"name":"template-installer","private":true,"type":"module"}\n')
|
|
36
|
+
run(['install', '--ignore-scripts', '--no-audit', '--no-fund', join(scratch, packed.filename)], installer)
|
|
37
|
+
const app = join(scratch, 'editable-app')
|
|
38
|
+
const cli = join(installer, 'node_modules/apptify/tooling/create-playground.mjs')
|
|
39
|
+
run(['exec', '--offline', '--', 'apptify-playground', app], installer)
|
|
40
|
+
let refused = false
|
|
41
|
+
try { execFileSync(process.execPath, [cli, app], { cwd: installer, stdio: 'pipe' }) } catch { refused = true }
|
|
42
|
+
assert(refused, 'Copy command must not overwrite a nonempty directory')
|
|
43
|
+
// Remove the installation used to invoke the command: the copied app must be portable.
|
|
44
|
+
await rm(installer, { recursive: true, force: true })
|
|
45
|
+
const appManifest = JSON.parse(await readFile(join(app, 'package.json'), 'utf8'))
|
|
46
|
+
assert(appManifest.dependencies.apptify.startsWith('file:./vendor/'))
|
|
47
|
+
assert((await readFile(join(app, '.gitignore'), 'utf8')).includes('node_modules/'))
|
|
48
|
+
for (const [path, hash] of Object.entries(snapshot.files)) {
|
|
49
|
+
if (path.startsWith('public/')) assert.equal(createHash('sha256').update(await readFile(join(app, path))).digest('hex'), hash)
|
|
50
|
+
}
|
|
51
|
+
run(['install', '--no-audit', '--no-fund'], app)
|
|
52
|
+
run(['run', 'typecheck'], app)
|
|
53
|
+
run(['run', 'build'], app)
|
|
54
|
+
|
|
55
|
+
// Verify the packed library's original consumer, without repository aliases.
|
|
56
|
+
const consumer = join(app, 'library-consumer')
|
|
57
|
+
await cp(join(root, 'tests/consumer'), consumer, { recursive: true })
|
|
58
|
+
await writeFile(join(consumer, 'vite.config.ts'), [
|
|
59
|
+
"import { defineConfig } from 'vite'",
|
|
60
|
+
"import vue from '@vitejs/plugin-vue'",
|
|
61
|
+
"import apptifyRoutes from 'apptify/vite'",
|
|
62
|
+
"import { fileURLToPath } from 'node:url'",
|
|
63
|
+
"export default defineConfig({ root: fileURLToPath(new URL('.', import.meta.url)), plugins: [apptifyRoutes(), vue()], build: { outDir: 'dist' } })", '',
|
|
64
|
+
].join('\n'))
|
|
65
|
+
execFileSync(process.execPath, [join(app, 'node_modules/vite/bin/vite.js'), 'build', '--config', join(consumer, 'vite.config.ts')], { cwd: app, stdio: 'inherit' })
|
|
66
|
+
const hasConsumerTypes = await readFile(join(consumer, 'image-api-types.ts')).then(() => true).catch(error => { if (error.code === 'ENOENT') return false; throw error })
|
|
67
|
+
if (hasConsumerTypes) {
|
|
68
|
+
await writeFile(join(consumer, 'tsconfig.json'), JSON.stringify({ extends: '../tsconfig.json', include: ['image-api-types.ts'] }, null, 2) + '\n')
|
|
69
|
+
execFileSync(process.execPath, [join(app, 'node_modules/vue-tsc/bin/vue-tsc.js'), '--noEmit', '-p', join(consumer, 'tsconfig.json')], { cwd: app, stdio: 'inherit' })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function serve() {
|
|
73
|
+
const child = spawn(npm, ['run', 'dev', '--', '--host', '127.0.0.1', '--port', '0', '--strictPort'], { cwd: app, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' })
|
|
74
|
+
const stop = () => {
|
|
75
|
+
if (process.platform !== 'win32') { try { process.kill(-child.pid, 'SIGTERM') } catch {} }
|
|
76
|
+
else child.kill('SIGTERM')
|
|
77
|
+
}
|
|
78
|
+
let output = ''
|
|
79
|
+
const url = await new Promise((resolve, reject) => {
|
|
80
|
+
const timer = setTimeout(() => reject(new Error('Dev server did not start: ' + output)), 20_000)
|
|
81
|
+
const collect = chunk => {
|
|
82
|
+
output += chunk
|
|
83
|
+
const match = output.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '').match(/http:\/\/127\.0\.0\.1:\d+\//)
|
|
84
|
+
if (match) { clearTimeout(timer); resolve(match[0]) }
|
|
85
|
+
}
|
|
86
|
+
child.stdout.on('data', collect)
|
|
87
|
+
child.stderr.on('data', collect)
|
|
88
|
+
child.once('exit', code => { clearTimeout(timer); reject(new Error('Dev server exited ' + code + ': ' + output)) })
|
|
89
|
+
}).catch(error => { stop(); throw error })
|
|
90
|
+
return { url, stop }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const browser = await chromium.launch({ channel: 'chrome' })
|
|
94
|
+
const errors = []
|
|
95
|
+
async function settledScreenshot(page, name) {
|
|
96
|
+
await page.evaluate(async () => { await new Promise(requestAnimationFrame); await new Promise(requestAnimationFrame) })
|
|
97
|
+
await page.waitForFunction(() => !document.getAnimations().some(animation => animation.playState === 'running' && Number.isFinite(Number(animation.effect?.getComputedTiming().endTime))))
|
|
98
|
+
await page.screenshot({ path: join(scratch, name) })
|
|
99
|
+
}
|
|
100
|
+
async function checkPage(baseURL, path, check) {
|
|
101
|
+
const page = await browser.newPage()
|
|
102
|
+
page.on('pageerror', error => errors.push(error.message))
|
|
103
|
+
await page.goto(new URL(path, baseURL).href)
|
|
104
|
+
await check(page)
|
|
105
|
+
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Page overflow: ' + path)
|
|
106
|
+
await page.close()
|
|
107
|
+
}
|
|
108
|
+
let server
|
|
109
|
+
try {
|
|
110
|
+
server = await serve()
|
|
111
|
+
await checkPage(server.url, '/', async page => {
|
|
112
|
+
await page.locator('.site-nav').waitFor()
|
|
113
|
+
await page.locator('main').waitFor()
|
|
114
|
+
await page.locator('.site-nav').getByRole('link', { name: '设置', exact: true }).click()
|
|
115
|
+
await page.waitForURL(new URL('/settings', server.url).href)
|
|
116
|
+
await page.goBack()
|
|
117
|
+
await page.waitForURL(server.url)
|
|
118
|
+
await settledScreenshot(page, 'complete-home.png')
|
|
119
|
+
})
|
|
120
|
+
await checkPage(server.url, '/components', async page => {
|
|
121
|
+
const image = page.locator('#apple-image img').first()
|
|
122
|
+
await image.scrollIntoViewIfNeeded()
|
|
123
|
+
assert.equal(await image.evaluate(async image => { await image.decode(); return image.naturalWidth > 0 }), true)
|
|
124
|
+
await page.locator('.component-index [aria-current]').first().waitFor()
|
|
125
|
+
})
|
|
126
|
+
await checkPage(server.url, '/settings', async page => {
|
|
127
|
+
await page.setViewportSize({ width: 320, height: 900 })
|
|
128
|
+
await page.getByRole('button', { name: '深色', exact: true }).click()
|
|
129
|
+
await page.getByRole('slider', { name: '玻璃不透明度', exact: true }).waitFor()
|
|
130
|
+
await page.getByRole('button', { name: '打开用户菜单', exact: true }).click()
|
|
131
|
+
await page.getByRole('button', { name: /登录 Login/ }).click()
|
|
132
|
+
await page.getByLabel('电子邮箱', { exact: true }).fill('template@example.com')
|
|
133
|
+
await page.getByLabel('密码', { exact: true }).fill('template-password')
|
|
134
|
+
assert.equal(await page.locator('.user-auth-form input[type=password]').count(), 1)
|
|
135
|
+
await settledScreenshot(page, 'settings-login-320.png')
|
|
136
|
+
})
|
|
137
|
+
await checkPage(server.url, '/template-missing-page', async page => {
|
|
138
|
+
await page.emulateMedia({ reducedMotion: 'reduce' })
|
|
139
|
+
await page.getByRole('heading', { name: '页面不存在', exact: true }).waitFor()
|
|
140
|
+
await page.locator('.paper-scene__art').waitFor()
|
|
141
|
+
await settledScreenshot(page, '404-current.png')
|
|
142
|
+
await page.getByRole('link', { name: '返回首页', exact: true }).click()
|
|
143
|
+
await page.waitForURL(server.url)
|
|
144
|
+
})
|
|
145
|
+
for (const path of Object.keys(snapshot.files).filter(path => path.startsWith('public/'))) {
|
|
146
|
+
const response = await fetch(new URL(path.slice('public/'.length), server.url))
|
|
147
|
+
assert.equal(response.status, 200, 'Missing served resource: ' + path)
|
|
148
|
+
assert.equal(createHash('sha256').update(Buffer.from(await response.arrayBuffer())).digest('hex'), snapshot.files[path])
|
|
149
|
+
}
|
|
150
|
+
server.stop()
|
|
151
|
+
server = undefined
|
|
152
|
+
|
|
153
|
+
await rm(join(app, 'playground/views/index.vue'))
|
|
154
|
+
await rm(join(app, 'playground/views/components.vue'))
|
|
155
|
+
// These files only served the removed overview page. Deleting them must be optional.
|
|
156
|
+
await rm(join(app, 'playground/ComponentDemo.vue'))
|
|
157
|
+
await rm(join(app, 'playground/catalog.ts'))
|
|
158
|
+
await rm(join(app, 'playground/components/ComponentIndex.vue'))
|
|
159
|
+
run(['run', 'typecheck'], app)
|
|
160
|
+
run(['run', 'build'], app)
|
|
161
|
+
server = await serve()
|
|
162
|
+
await checkPage(server.url, '/settings', async page => {
|
|
163
|
+
assert.equal(await page.locator('.site-nav a.apple-navibar__item').count(), 1)
|
|
164
|
+
assert.equal(await page.locator('.site-nav .brand').getAttribute('href'), '/settings')
|
|
165
|
+
await page.getByRole('button', { name: '打开用户菜单', exact: true }).click()
|
|
166
|
+
await page.getByRole('dialog', { name: '用户菜单', exact: true }).waitFor()
|
|
167
|
+
await settledScreenshot(page, 'reduced-settings.png')
|
|
168
|
+
})
|
|
169
|
+
await checkPage(server.url, '/new-business-page', async page => {
|
|
170
|
+
await page.getByRole('heading', { name: '页面不存在', exact: true }).waitFor()
|
|
171
|
+
assert.equal(await page.getByRole('link', { name: '返回首页', exact: true }).getAttribute('href'), '/settings')
|
|
172
|
+
await page.getByRole('link', { name: '返回首页', exact: true }).click()
|
|
173
|
+
await page.waitForURL(new URL('/settings', server.url).href)
|
|
174
|
+
})
|
|
175
|
+
assert.deepEqual(errors, [], 'Browser runtime errors')
|
|
176
|
+
} finally {
|
|
177
|
+
server?.stop()
|
|
178
|
+
await browser.close()
|
|
179
|
+
}
|
|
180
|
+
for (const [path, hash] of Object.entries(snapshot.files)) {
|
|
181
|
+
assert.equal(createHash('sha256').update(await readFile(join(root, path))).digest('hex'), hash, 'Source changed during packing; rerun for its latest snapshot: ' + path)
|
|
182
|
+
}
|
|
183
|
+
for (const [path, hash] of Object.entries(snapshot.buildSources || {})) {
|
|
184
|
+
assert.equal(createHash('sha256').update(await readFile(join(root, path))).digest('hex'), hash, 'Build source changed after this archive was created; pack the latest version: ' + path)
|
|
185
|
+
}
|
|
186
|
+
if (archiveInput) assert.equal(createHash('sha256').update(await readFile(archiveInput)).digest('hex'), archiveSha256, 'Archive was overwritten during acceptance')
|
|
187
|
+
await writeFile(join(scratch, 'acceptance.json'), JSON.stringify({ package: packed.filename, archiveInput, archiveSha256, packageBytes: packed.size, packageFileCount: packed.files.length, templateSourceFiles: Object.keys(snapshot.files).length, completeTemplate: true, removedDemoPages: true, libraryConsumer: true, libraryConsumerTypes: hasConsumerTypes, browserErrors: errors, nodeVersion: process.version, sourceSnapshot: snapshot }, null, 2) + '\n')
|
|
188
|
+
console.log('Complete editable template, reduced app, routes/resources/account/settings/404, and packed library consumer all passed.\nEvidence: ' + scratch)
|
package/tooling/vite.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Pages from 'vite-plugin-pages'
|
|
2
|
+
|
|
3
|
+
/** File routing with the same src/views convention as vuetify-project. */
|
|
4
|
+
export default function apptifyRoutes(options = {}) {
|
|
5
|
+
return Pages({
|
|
6
|
+
dirs: [{ dir: 'src/views', baseRoute: '' }],
|
|
7
|
+
...options,
|
|
8
|
+
resolver: 'vue',
|
|
9
|
+
})
|
|
10
|
+
}
|