create-next-imagicma 0.0.4 → 0.0.6

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 (106) hide show
  1. package/README.md +30 -13
  2. package/bin/create-next-imagicma.mjs +138 -22
  3. package/package.json +2 -1
  4. package/template/app/globals.css +331 -0
  5. package/template/app/layout.tsx +4 -6
  6. package/template/app/page.tsx +18 -40
  7. package/template/package.json +1 -1
  8. package/template/public/imagicma-picker-bridge.js +374 -0
  9. package/template-hono/.env.example +8 -0
  10. package/template-hono/.imagicma/AGENTS.md +7 -0
  11. package/template-hono/.imagicma/port.json +5 -0
  12. package/template-hono/AGENTS.md +39 -0
  13. package/template-hono/README.md +48 -0
  14. package/template-hono/client/src/App.tsx +13 -0
  15. package/template-hono/client/src/components/ErrorBoundary.tsx +74 -0
  16. package/template-hono/client/src/components/HelloClient.tsx +69 -0
  17. package/template-hono/client/src/globals.css +767 -0
  18. package/template-hono/client/src/main.tsx +24 -0
  19. package/template-hono/client/src/pages/HelloPage.tsx +22 -0
  20. package/template-hono/client/src/pages/HomePage.tsx +30 -0
  21. package/template-hono/client/src/providers.tsx +21 -0
  22. package/template-hono/components/ui/accordion.tsx +58 -0
  23. package/template-hono/components/ui/alert-dialog.tsx +141 -0
  24. package/template-hono/components/ui/alert.tsx +61 -0
  25. package/template-hono/components/ui/aspect-ratio.tsx +7 -0
  26. package/template-hono/components/ui/avatar.tsx +51 -0
  27. package/template-hono/components/ui/badge.tsx +40 -0
  28. package/template-hono/components/ui/breadcrumb.tsx +117 -0
  29. package/template-hono/components/ui/button.tsx +64 -0
  30. package/template-hono/components/ui/calendar.tsx +72 -0
  31. package/template-hono/components/ui/card.tsx +87 -0
  32. package/template-hono/components/ui/carousel.tsx +262 -0
  33. package/template-hono/components/ui/chart.tsx +365 -0
  34. package/template-hono/components/ui/checkbox.tsx +30 -0
  35. package/template-hono/components/ui/collapsible.tsx +11 -0
  36. package/template-hono/components/ui/command.tsx +153 -0
  37. package/template-hono/components/ui/context-menu.tsx +200 -0
  38. package/template-hono/components/ui/dialog.tsx +122 -0
  39. package/template-hono/components/ui/drawer.tsx +118 -0
  40. package/template-hono/components/ui/dropdown-menu.tsx +200 -0
  41. package/template-hono/components/ui/form.tsx +178 -0
  42. package/template-hono/components/ui/hover-card.tsx +29 -0
  43. package/template-hono/components/ui/input-otp.tsx +71 -0
  44. package/template-hono/components/ui/input.tsx +25 -0
  45. package/template-hono/components/ui/label.tsx +26 -0
  46. package/template-hono/components/ui/menubar.tsx +256 -0
  47. package/template-hono/components/ui/navigation-menu.tsx +130 -0
  48. package/template-hono/components/ui/pagination.tsx +119 -0
  49. package/template-hono/components/ui/popover.tsx +31 -0
  50. package/template-hono/components/ui/progress.tsx +28 -0
  51. package/template-hono/components/ui/radio-group.tsx +44 -0
  52. package/template-hono/components/ui/resizable.tsx +45 -0
  53. package/template-hono/components/ui/scroll-area.tsx +48 -0
  54. package/template-hono/components/ui/select.tsx +160 -0
  55. package/template-hono/components/ui/separator.tsx +31 -0
  56. package/template-hono/components/ui/sheet.tsx +140 -0
  57. package/template-hono/components/ui/sidebar.tsx +732 -0
  58. package/template-hono/components/ui/skeleton.tsx +17 -0
  59. package/template-hono/components/ui/slider.tsx +28 -0
  60. package/template-hono/components/ui/switch.tsx +29 -0
  61. package/template-hono/components/ui/table.tsx +119 -0
  62. package/template-hono/components/ui/tabs.tsx +55 -0
  63. package/template-hono/components/ui/textarea.tsx +24 -0
  64. package/template-hono/components/ui/toast.tsx +129 -0
  65. package/template-hono/components/ui/toaster.tsx +35 -0
  66. package/template-hono/components/ui/toggle-group.tsx +61 -0
  67. package/template-hono/components/ui/toggle.tsx +45 -0
  68. package/template-hono/components/ui/tooltip.tsx +30 -0
  69. package/template-hono/drizzle.config.ts +50 -0
  70. package/template-hono/eslint.config.mjs +13 -0
  71. package/template-hono/gitignore +40 -0
  72. package/template-hono/hooks/use-greeting.ts +15 -0
  73. package/template-hono/hooks/use-mobile.ts +21 -0
  74. package/template-hono/hooks/use-toast.ts +194 -0
  75. package/template-hono/index.html +13 -0
  76. package/template-hono/lib/queryClient.ts +59 -0
  77. package/template-hono/lib/theme/default-theme.ts +11 -0
  78. package/template-hono/lib/utils.ts +6 -0
  79. package/template-hono/package.json +82 -0
  80. package/template-hono/pnpm-lock.yaml +5162 -0
  81. package/template-hono/postcss.config.mjs +7 -0
  82. package/template-hono/process-compose.yaml +13 -0
  83. package/template-hono/public/favicon.ico +0 -0
  84. package/template-hono/public/file.svg +1 -0
  85. package/template-hono/public/globe.svg +1 -0
  86. package/template-hono/public/imagicma-picker-bridge.js +374 -0
  87. package/template-hono/public/next.svg +1 -0
  88. package/template-hono/public/vercel.svg +1 -0
  89. package/template-hono/public/window.svg +1 -0
  90. package/template-hono/scripts/imagicma-common.mjs +118 -0
  91. package/template-hono/scripts/imagicma-dev.mjs +29 -0
  92. package/template-hono/scripts/imagicma-guard.mjs +17 -0
  93. package/template-hono/scripts/imagicma-start.mjs +24 -0
  94. package/template-hono/server/app.ts +40 -0
  95. package/template-hono/server/db.ts +22 -0
  96. package/template-hono/server/dev-app.ts +5 -0
  97. package/template-hono/server/index.ts +94 -0
  98. package/template-hono/server/routes/greeting.ts +25 -0
  99. package/template-hono/server/storage.ts +39 -0
  100. package/template-hono/shared/routes.ts +13 -0
  101. package/template-hono/shared/schema.ts +17 -0
  102. package/template-hono/tailwind.config.mjs +97 -0
  103. package/template-hono/tsconfig.json +39 -0
  104. package/template-hono/tsconfig.server.json +15 -0
  105. package/template-hono/types/pg.d.ts +19 -0
  106. package/template-hono/vite.config.ts +125 -0
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,13 @@
1
+ version: "0.5"
2
+ log_location: ".opencode/logs/process-compose.log"
3
+
4
+ processes:
5
+ web:
6
+ command: "pnpm dev"
7
+ working_dir: "."
8
+ environment:
9
+ - "PORT=5001"
10
+ availability:
11
+ restart: "always"
12
+ backoff_seconds: 5
13
+ max_restarts: 20
Binary file
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -0,0 +1,374 @@
1
+ (function () {
2
+ var CHANNEL = 'imagicma.preview-picker';
3
+ var VERSION = 1;
4
+ var MAX_TEXT_LENGTH = 240;
5
+ var MAX_SELECTOR_LENGTH = 512;
6
+ var PROD_PARENT_ORIGIN = 'https://www.imagicma.cn';
7
+ var LOCAL_PARENT_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i;
8
+ var LOCAL_IMAGICMA_PARENT_RE = /^https?:\/\/([a-z0-9-]+\.)?local\.imagicma\.cn(:\d+)?$/i;
9
+
10
+ var state = {
11
+ active: false,
12
+ sessionId: '',
13
+ nonce: '',
14
+ parentOrigin: '',
15
+ hoverEl: null,
16
+ overlayEl: null,
17
+ rafId: 0,
18
+ };
19
+
20
+ var listenersBound = false;
21
+
22
+ function isAllowedParentOrigin(origin) {
23
+ if (!origin) return false;
24
+ return origin === PROD_PARENT_ORIGIN || LOCAL_PARENT_RE.test(origin) || LOCAL_IMAGICMA_PARENT_RE.test(origin);
25
+ }
26
+
27
+ function isRecord(value) {
28
+ return typeof value === 'object' && value !== null;
29
+ }
30
+
31
+ function trimText(value) {
32
+ return typeof value === 'string' ? value.trim() : '';
33
+ }
34
+
35
+ function truncateText(value, max) {
36
+ if (!value) return '';
37
+ return value.length > max ? value.slice(0, max) : value;
38
+ }
39
+
40
+ function safePostMessage(type, payload) {
41
+ if (!state.parentOrigin) return;
42
+ if (window.parent === window) return;
43
+
44
+ try {
45
+ window.parent.postMessage(
46
+ {
47
+ channel: CHANNEL,
48
+ version: VERSION,
49
+ type: type,
50
+ sessionId: state.sessionId,
51
+ nonce: state.nonce,
52
+ payload: payload,
53
+ },
54
+ state.parentOrigin,
55
+ );
56
+ } catch (_error) {
57
+ // Ignore postMessage failures.
58
+ }
59
+ }
60
+
61
+ function ensureOverlay() {
62
+ if (state.overlayEl && document.body.contains(state.overlayEl)) return state.overlayEl;
63
+ var overlay = document.createElement('div');
64
+ overlay.setAttribute('data-imagicma-picker-overlay', 'true');
65
+ overlay.style.position = 'fixed';
66
+ overlay.style.left = '0';
67
+ overlay.style.top = '0';
68
+ overlay.style.width = '0';
69
+ overlay.style.height = '0';
70
+ overlay.style.zIndex = '2147483647';
71
+ overlay.style.pointerEvents = 'none';
72
+ overlay.style.border = '2px solid #1d4ed8';
73
+ overlay.style.background = 'rgba(29, 78, 216, 0.10)';
74
+ overlay.style.boxSizing = 'border-box';
75
+ overlay.style.display = 'none';
76
+ document.body.appendChild(overlay);
77
+ state.overlayEl = overlay;
78
+ return overlay;
79
+ }
80
+
81
+ function hideOverlay() {
82
+ if (!state.overlayEl) return;
83
+ state.overlayEl.style.display = 'none';
84
+ }
85
+
86
+ function updateOverlay() {
87
+ if (!state.active || !state.hoverEl) {
88
+ hideOverlay();
89
+ return;
90
+ }
91
+
92
+ var rect = state.hoverEl.getBoundingClientRect();
93
+ var overlay = ensureOverlay();
94
+ if (rect.width <= 0 || rect.height <= 0) {
95
+ overlay.style.display = 'none';
96
+ return;
97
+ }
98
+
99
+ overlay.style.display = 'block';
100
+ overlay.style.left = rect.left + 'px';
101
+ overlay.style.top = rect.top + 'px';
102
+ overlay.style.width = rect.width + 'px';
103
+ overlay.style.height = rect.height + 'px';
104
+ }
105
+
106
+ function queueOverlayUpdate() {
107
+ if (state.rafId) return;
108
+ state.rafId = window.requestAnimationFrame(function () {
109
+ state.rafId = 0;
110
+ updateOverlay();
111
+ });
112
+ }
113
+
114
+ function isValidElementTarget(target) {
115
+ if (!(target instanceof Element)) return false;
116
+ if (state.overlayEl && state.overlayEl.contains(target)) return false;
117
+ return true;
118
+ }
119
+
120
+ function cssEscapeIdent(value) {
121
+ if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape === 'function') {
122
+ return window.CSS.escape(value);
123
+ }
124
+ return value.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
125
+ }
126
+
127
+ function selectorByClassChain(element) {
128
+ var tokens = [];
129
+ if (element.classList && element.classList.length > 0) {
130
+ for (var i = 0; i < element.classList.length && i < 3; i += 1) {
131
+ var cls = trimText(element.classList[i]);
132
+ if (cls) tokens.push('.' + cssEscapeIdent(cls));
133
+ }
134
+ }
135
+ return tokens.length > 0 ? element.tagName.toLowerCase() + tokens.join('') : '';
136
+ }
137
+
138
+ function selectorByPath(element) {
139
+ var parts = [];
140
+ var current = element;
141
+ var depth = 0;
142
+ while (current && current.nodeType === 1 && depth < 6) {
143
+ var part = current.tagName.toLowerCase();
144
+ if (current.id) {
145
+ part += '#' + cssEscapeIdent(current.id);
146
+ parts.unshift(part);
147
+ break;
148
+ }
149
+
150
+ var classSelector = selectorByClassChain(current);
151
+ if (classSelector) {
152
+ part = classSelector;
153
+ } else if (current.parentElement) {
154
+ var siblings = current.parentElement.children;
155
+ var sameTagIndex = 0;
156
+ var sameTagCount = 0;
157
+ for (var i = 0; i < siblings.length; i += 1) {
158
+ var sibling = siblings[i];
159
+ if (sibling.tagName === current.tagName) {
160
+ sameTagCount += 1;
161
+ if (sibling === current) {
162
+ sameTagIndex = sameTagCount;
163
+ }
164
+ }
165
+ }
166
+ if (sameTagCount > 1 && sameTagIndex > 0) {
167
+ part += ':nth-of-type(' + sameTagIndex + ')';
168
+ }
169
+ }
170
+
171
+ parts.unshift(part);
172
+ current = current.parentElement;
173
+ depth += 1;
174
+ }
175
+
176
+ return parts.join(' > ');
177
+ }
178
+
179
+ function getElementSelector(element) {
180
+ if (element.id) {
181
+ return '#' + cssEscapeIdent(element.id);
182
+ }
183
+
184
+ var testId = element.getAttribute('data-testid');
185
+ if (testId) {
186
+ return '[data-testid="' + testId.replace(/"/g, '\\"') + '"]';
187
+ }
188
+
189
+ var ariaLabel = element.getAttribute('aria-label');
190
+ if (ariaLabel) {
191
+ return element.tagName.toLowerCase() + '[aria-label="' + ariaLabel.replace(/"/g, '\\"') + '"]';
192
+ }
193
+
194
+ var role = element.getAttribute('role');
195
+ if (role) {
196
+ return element.tagName.toLowerCase() + '[role="' + role.replace(/"/g, '\\"') + '"]';
197
+ }
198
+
199
+ var byClass = selectorByClassChain(element);
200
+ if (byClass) return byClass;
201
+
202
+ return selectorByPath(element) || element.tagName.toLowerCase();
203
+ }
204
+
205
+ function buildSelectionPayload(element) {
206
+ var rect = element.getBoundingClientRect();
207
+ var selector = truncateText(getElementSelector(element), MAX_SELECTOR_LENGTH);
208
+ var textSource = trimText(element.innerText || element.textContent || '');
209
+ var attributes = {};
210
+
211
+ function putAttr(name, value) {
212
+ if (!name || typeof value !== 'string') return;
213
+ var key = trimText(name).toLowerCase();
214
+ var val = trimText(value);
215
+ if (!key || !val) return;
216
+ attributes[key] = truncateText(val, 240);
217
+ }
218
+
219
+ putAttr('id', element.id || '');
220
+ putAttr('class', element.className || '');
221
+ putAttr('role', element.getAttribute('role') || '');
222
+ putAttr('name', element.getAttribute('name') || '');
223
+ putAttr('type', element.getAttribute('type') || '');
224
+ putAttr('title', element.getAttribute('title') || '');
225
+ putAttr('aria-label', element.getAttribute('aria-label') || '');
226
+ putAttr('aria-labelledby', element.getAttribute('aria-labelledby') || '');
227
+ putAttr('aria-describedby', element.getAttribute('aria-describedby') || '');
228
+ putAttr('placeholder', element.getAttribute('placeholder') || '');
229
+ putAttr('href', element.getAttribute('href') || '');
230
+ putAttr('src', element.getAttribute('src') || '');
231
+ putAttr('alt', element.getAttribute('alt') || '');
232
+
233
+ if (element.attributes && element.attributes.length > 0) {
234
+ for (var i = 0; i < element.attributes.length; i += 1) {
235
+ var attr = element.attributes[i];
236
+ if (!attr) continue;
237
+ var attrName = trimText(attr.name || '').toLowerCase();
238
+ if (!attrName) continue;
239
+ if (attrName.indexOf('data-') === 0 || attrName.indexOf('aria-') === 0) {
240
+ putAttr(attrName, attr.value || '');
241
+ }
242
+ }
243
+ }
244
+
245
+ return {
246
+ pageUrl: window.location.href,
247
+ selector: selector,
248
+ tagName: element.tagName.toLowerCase(),
249
+ textSnippet: truncateText(textSource, MAX_TEXT_LENGTH),
250
+ attributes: attributes,
251
+ rect: {
252
+ x: Number(rect.left.toFixed(2)),
253
+ y: Number(rect.top.toFixed(2)),
254
+ width: Number(rect.width.toFixed(2)),
255
+ height: Number(rect.height.toFixed(2)),
256
+ },
257
+ };
258
+ }
259
+
260
+ function handleMouseMove(event) {
261
+ if (!state.active) return;
262
+ var target = event.target;
263
+ if (!isValidElementTarget(target)) return;
264
+ state.hoverEl = target;
265
+ queueOverlayUpdate();
266
+ }
267
+
268
+ function handleScrollOrResize() {
269
+ if (!state.active) return;
270
+ queueOverlayUpdate();
271
+ }
272
+
273
+ function handleClickCapture(event) {
274
+ if (!state.active) return;
275
+ var target = event.target;
276
+ if (!isValidElementTarget(target)) return;
277
+
278
+ event.preventDefault();
279
+ event.stopPropagation();
280
+ if (typeof event.stopImmediatePropagation === 'function') {
281
+ event.stopImmediatePropagation();
282
+ }
283
+
284
+ try {
285
+ var payload = buildSelectionPayload(target);
286
+ safePostMessage('IMAGICMA_PICKER_SELECTED', payload);
287
+ } catch (error) {
288
+ safePostMessage('IMAGICMA_PICKER_ERROR', {
289
+ message: error instanceof Error ? error.message : 'Failed to build selected element payload',
290
+ });
291
+ }
292
+ }
293
+
294
+ function bindInteractionListeners() {
295
+ if (listenersBound) return;
296
+ listenersBound = true;
297
+ document.addEventListener('mousemove', handleMouseMove, true);
298
+ document.addEventListener('click', handleClickCapture, true);
299
+ window.addEventListener('scroll', handleScrollOrResize, true);
300
+ window.addEventListener('resize', handleScrollOrResize, true);
301
+ }
302
+
303
+ function unbindInteractionListeners() {
304
+ if (!listenersBound) return;
305
+ listenersBound = false;
306
+ document.removeEventListener('mousemove', handleMouseMove, true);
307
+ document.removeEventListener('click', handleClickCapture, true);
308
+ window.removeEventListener('scroll', handleScrollOrResize, true);
309
+ window.removeEventListener('resize', handleScrollOrResize, true);
310
+ }
311
+
312
+ function activatePicking() {
313
+ state.active = true;
314
+ state.hoverEl = null;
315
+ bindInteractionListeners();
316
+ document.documentElement.style.cursor = 'crosshair';
317
+ document.body.style.cursor = 'crosshair';
318
+ safePostMessage('IMAGICMA_PICKER_READY', {
319
+ pageUrl: window.location.href,
320
+ });
321
+ }
322
+
323
+ function deactivatePicking() {
324
+ state.active = false;
325
+ state.hoverEl = null;
326
+ if (state.rafId) {
327
+ window.cancelAnimationFrame(state.rafId);
328
+ state.rafId = 0;
329
+ }
330
+ hideOverlay();
331
+ unbindInteractionListeners();
332
+ document.documentElement.style.cursor = '';
333
+ document.body.style.cursor = '';
334
+ }
335
+
336
+ function isValidMessage(data) {
337
+ if (!isRecord(data)) return false;
338
+ if (data.channel !== CHANNEL) return false;
339
+ if (data.version !== VERSION) return false;
340
+ if (typeof data.type !== 'string') return false;
341
+ if (typeof data.sessionId !== 'string' || data.sessionId.length === 0) return false;
342
+ if (typeof data.nonce !== 'string' || data.nonce.length === 0) return false;
343
+ return true;
344
+ }
345
+
346
+ function handleParentMessage(event) {
347
+ if (!isAllowedParentOrigin(event.origin)) return;
348
+ if (!isValidMessage(event.data)) return;
349
+
350
+ var data = event.data;
351
+ state.parentOrigin = event.origin;
352
+ state.sessionId = data.sessionId;
353
+ state.nonce = data.nonce;
354
+
355
+ if (data.type === 'IMAGICMA_PICKER_PING') {
356
+ safePostMessage('IMAGICMA_PICKER_READY', {
357
+ pageUrl: window.location.href,
358
+ });
359
+ return;
360
+ }
361
+
362
+ if (data.type === 'IMAGICMA_PICKER_START') {
363
+ activatePicking();
364
+ return;
365
+ }
366
+
367
+ if (data.type === 'IMAGICMA_PICKER_STOP') {
368
+ deactivatePicking();
369
+ }
370
+ }
371
+
372
+ window.addEventListener('message', handleParentMessage);
373
+ window.addEventListener('beforeunload', deactivatePicking);
374
+ })();
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
@@ -0,0 +1,118 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ export const ROOT_DIR = path.resolve(__dirname, "..");
10
+ export const PORT_FILE = path.join(ROOT_DIR, ".imagicma", "port.json");
11
+ export const LAUNCH_TOKEN_FILE = path.join(
12
+ ROOT_DIR,
13
+ ".imagicma",
14
+ "launch-token.json",
15
+ );
16
+ export const PACKAGE_JSON_FILE = path.join(ROOT_DIR, "package.json");
17
+
18
+ export const CANONICAL_SCRIPTS = {
19
+ predev: "node ./scripts/imagicma-guard.mjs dev",
20
+ dev: "node ./scripts/imagicma-dev.mjs",
21
+ prestart: "node ./scripts/imagicma-guard.mjs start",
22
+ start: "node ./scripts/imagicma-start.mjs",
23
+ };
24
+
25
+ function isValidPort(value) {
26
+ return Number.isInteger(value) && value >= 1 && value <= 65535;
27
+ }
28
+
29
+ export async function readLockedPort() {
30
+ const raw = await fs.readFile(PORT_FILE, "utf8");
31
+ const data = JSON.parse(raw);
32
+ const port = Number(data?.port);
33
+
34
+ if (!isValidPort(port)) {
35
+ throw new Error(
36
+ `无效端口配置:${PORT_FILE},port=${JSON.stringify(data?.port)}`,
37
+ );
38
+ }
39
+
40
+ if (data?.locked !== true) {
41
+ throw new Error(`端口锁文件未锁定:${PORT_FILE}(locked 必须为 true)`);
42
+ }
43
+
44
+ return port;
45
+ }
46
+
47
+ export async function readPackageJson() {
48
+ const raw = await fs.readFile(PACKAGE_JSON_FILE, "utf8");
49
+ const pkg = JSON.parse(raw);
50
+ return { raw, pkg };
51
+ }
52
+
53
+ export async function writePackageJson(pkg) {
54
+ await fs.writeFile(PACKAGE_JSON_FILE, `${JSON.stringify(pkg, null, 2)}\n`);
55
+ }
56
+
57
+ export async function writeLaunchToken(mode) {
58
+ await fs.mkdir(path.dirname(LAUNCH_TOKEN_FILE), { recursive: true });
59
+ const expiresAt = Date.now() + 30 * 1000;
60
+ const token = {
61
+ mode,
62
+ nonce: crypto.randomUUID(),
63
+ expiresAt,
64
+ };
65
+ await fs.writeFile(LAUNCH_TOKEN_FILE, `${JSON.stringify(token, null, 2)}\n`);
66
+ return token;
67
+ }
68
+
69
+ export async function consumeLaunchToken(mode) {
70
+ let raw;
71
+ try {
72
+ raw = await fs.readFile(LAUNCH_TOKEN_FILE, "utf8");
73
+ } catch (error) {
74
+ if (error && typeof error === "object" && error.code === "ENOENT") {
75
+ return false;
76
+ }
77
+ throw error;
78
+ }
79
+
80
+ try {
81
+ const token = JSON.parse(raw);
82
+ const isValid =
83
+ token?.mode === mode &&
84
+ Number.isInteger(token?.expiresAt) &&
85
+ token.expiresAt >= Date.now();
86
+
87
+ if (!isValid) {
88
+ await fs.rm(LAUNCH_TOKEN_FILE, { force: true });
89
+ return false;
90
+ }
91
+
92
+ await fs.rm(LAUNCH_TOKEN_FILE, { force: true });
93
+ return true;
94
+ } catch {
95
+ await fs.rm(LAUNCH_TOKEN_FILE, { force: true });
96
+ return false;
97
+ }
98
+ }
99
+
100
+ export async function ensureCanonicalScripts() {
101
+ const { pkg } = await readPackageJson();
102
+ const currentScripts = pkg.scripts ?? {};
103
+
104
+ let mutated = false;
105
+ for (const [name, expected] of Object.entries(CANONICAL_SCRIPTS)) {
106
+ if (currentScripts[name] !== expected) {
107
+ mutated = true;
108
+ currentScripts[name] = expected;
109
+ }
110
+ }
111
+
112
+ if (mutated) {
113
+ pkg.scripts = currentScripts;
114
+ await writePackageJson(pkg);
115
+ }
116
+
117
+ return mutated;
118
+ }
@@ -0,0 +1,29 @@
1
+ import { spawn } from "node:child_process";
2
+ import path from "node:path";
3
+ import { ROOT_DIR, readLockedPort } from "./imagicma-common.mjs";
4
+
5
+ const viteBin = path.join(ROOT_DIR, "node_modules", "vite", "bin", "vite.js");
6
+ const port = await readLockedPort();
7
+
8
+ const child = spawn(
9
+ process.execPath,
10
+ [viteBin, "--host", "0.0.0.0", "--port", String(port), "--strictPort"],
11
+ {
12
+ cwd: ROOT_DIR,
13
+ stdio: "inherit",
14
+ env: {
15
+ ...process.env,
16
+ IMAGICMA_SCRIPT_LAUNCH: "1",
17
+ IMAGICMA_LAUNCH_MODE: "dev",
18
+ },
19
+ },
20
+ );
21
+
22
+ child.on("close", (code) => {
23
+ process.exit(code ?? 0);
24
+ });
25
+
26
+ child.on("error", (error) => {
27
+ console.error(`[imagicma] 启动 dev 失败:${error.message}`);
28
+ process.exit(1);
29
+ });
@@ -0,0 +1,17 @@
1
+ import { ensureCanonicalScripts, writeLaunchToken } from "./imagicma-common.mjs";
2
+
3
+ const mode = process.argv[2];
4
+
5
+ if (mode !== "dev" && mode !== "start") {
6
+ console.error("[imagicma] guard 参数错误,仅支持 dev/start");
7
+ process.exit(1);
8
+ }
9
+
10
+ const repaired = await ensureCanonicalScripts();
11
+ if (repaired) {
12
+ console.warn(
13
+ "[imagicma] 检测到 package.json scripts 被修改,已自动恢复为受保护启动命令。",
14
+ );
15
+ }
16
+
17
+ await writeLaunchToken(mode);
@@ -0,0 +1,24 @@
1
+ import { spawn } from "node:child_process";
2
+ import path from "node:path";
3
+ import { ROOT_DIR } from "./imagicma-common.mjs";
4
+
5
+ const entry = path.join(ROOT_DIR, "dist", "server", "index.js");
6
+
7
+ const child = spawn(process.execPath, [entry], {
8
+ cwd: ROOT_DIR,
9
+ stdio: "inherit",
10
+ env: {
11
+ ...process.env,
12
+ IMAGICMA_SCRIPT_LAUNCH: "1",
13
+ IMAGICMA_LAUNCH_MODE: "start",
14
+ },
15
+ });
16
+
17
+ child.on("close", (code) => {
18
+ process.exit(code ?? 0);
19
+ });
20
+
21
+ child.on("error", (error) => {
22
+ console.error(`[imagicma] 启动 start 失败:${error.message}`);
23
+ process.exit(1);
24
+ });
@@ -0,0 +1,40 @@
1
+ import { Hono } from "hono";
2
+ import { serveStatic } from "@hono/node-server/serve-static";
3
+ import { greetingRoute } from "./routes/greeting";
4
+
5
+ type CreateAppOptions = {
6
+ serveClient?: boolean;
7
+ };
8
+
9
+ export function createApp({ serveClient = false }: CreateAppOptions = {}) {
10
+ const app = new Hono();
11
+
12
+ app.route("/", greetingRoute);
13
+
14
+ if (serveClient) {
15
+ const serveClientFiles = serveStatic({ root: "./dist/client" });
16
+ const serveClientIndex = serveStatic({ path: "./dist/client/index.html" });
17
+
18
+ app.use("/assets/*", serveClientFiles);
19
+ app.use("/imagicma-picker-bridge.js", serveClientFiles);
20
+ app.use("/*.svg", serveClientFiles);
21
+ app.use("/*.ico", serveClientFiles);
22
+ app.use("/*.png", serveClientFiles);
23
+ app.use("/*.jpg", serveClientFiles);
24
+ app.use("/*.jpeg", serveClientFiles);
25
+ app.use("/*.webp", serveClientFiles);
26
+ app.use("/*.txt", serveClientFiles);
27
+ app.use("/*.js", serveClientFiles);
28
+ app.use("/*.css", serveClientFiles);
29
+
30
+ app.get("*", async (c, next) => {
31
+ if (c.req.path.startsWith("/api/")) {
32
+ return c.notFound();
33
+ }
34
+
35
+ return serveClientIndex(c, next);
36
+ });
37
+ }
38
+
39
+ return app;
40
+ }
@@ -0,0 +1,22 @@
1
+ import { drizzle } from "drizzle-orm/node-postgres";
2
+ import pg from "pg";
3
+ import * as schema from "../shared/schema";
4
+
5
+ const { Pool } = pg;
6
+
7
+ const databaseUrl = process.env.DATABASE_URL;
8
+
9
+ declare global {
10
+ var __dbPool: InstanceType<typeof Pool> | undefined;
11
+ }
12
+
13
+ export const pool =
14
+ databaseUrl
15
+ ? globalThis.__dbPool ?? new Pool({ connectionString: databaseUrl })
16
+ : undefined;
17
+
18
+ if (databaseUrl && process.env.NODE_ENV !== "production") {
19
+ globalThis.__dbPool = pool;
20
+ }
21
+
22
+ export const db = pool ? drizzle(pool, { schema }) : undefined;
@@ -0,0 +1,5 @@
1
+ import { createApp } from "./app";
2
+
3
+ const app = createApp();
4
+
5
+ export default app;