@inkly-org/cli 0.5.1

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.
@@ -0,0 +1,406 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>inkly dev — hub</title>
7
+ <!-- Default to the Inkly platform favicon; replaced below if the hub
8
+ sets its own `brand.favicon`. -->
9
+ <link rel="icon" href="https://www.inklyai.dev/favicon.ico" />
10
+ <!-- Same runtime.css the per-demo viewer uses. It now also ships the
11
+ `.inkly-hub-index` styles so the CLI hub index, platform hub
12
+ viewer, and future CDN viewer all render from the same CSS. -->
13
+ <link rel="stylesheet" href="/__inkly/runtime.css" />
14
+ <script type="importmap">{"imports":{"react":"/__inkly/vendor/react.mjs","react-dom":"/__inkly/vendor/react-dom.mjs","react-dom/client":"/__inkly/vendor/react-dom-client.mjs","react/jsx-runtime":"/__inkly/vendor/react-jsx-runtime.mjs"}}</script>
15
+ <link rel="modulepreload" href="/__inkly/vendor/react.mjs" crossorigin="anonymous" />
16
+ <link rel="modulepreload" href="/__inkly/vendor/react-dom.mjs" crossorigin="anonymous" />
17
+ <link rel="modulepreload" href="/__inkly/vendor/react-dom-client.mjs" crossorigin="anonymous" />
18
+ <link rel="modulepreload" href="/__inkly/vendor/react-jsx-runtime.mjs" crossorigin="anonymous" />
19
+ <link rel="prefetch" href="/__inkly/runtime.js" as="script" crossorigin />
20
+ <script id="__inkly_hub_data" type="application/json">{"hub":{"name":""},"demos":[]}</script>
21
+ <style>
22
+ html, body {
23
+ margin: 0;
24
+ padding: 0;
25
+ width: 100%;
26
+ height: 100%;
27
+ min-height: 100%;
28
+ overflow: hidden;
29
+ }
30
+ body {
31
+ display: flex;
32
+ flex-direction: column;
33
+ }
34
+ #root {
35
+ flex: 1 1 auto;
36
+ min-height: 0;
37
+ height: 100%;
38
+ display: flex;
39
+ flex-direction: column;
40
+ overflow: hidden;
41
+ }
42
+ .inkly-hub-loading {
43
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
44
+ color: #1f1f1f;
45
+ }
46
+ .inkly-hub-skeleton {
47
+ min-height: 100vh;
48
+ background: #f7f7f7;
49
+ color: #1f1f1f;
50
+ }
51
+ .inkly-hub-skeleton-head {
52
+ position: sticky;
53
+ top: 0;
54
+ z-index: 1;
55
+ /* Match the 56px demo viewer header height. */
56
+ height: 56px;
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: space-between;
60
+ gap: 24px;
61
+ padding: 0 28px;
62
+ border-bottom: 1px solid #dedede;
63
+ background: rgba(252, 252, 252, 0.92);
64
+ backdrop-filter: blur(10px);
65
+ }
66
+ .inkly-hub-skeleton-brand,
67
+ .inkly-hub-skeleton-tabs,
68
+ .inkly-hub-skeleton-actions,
69
+ .inkly-hub-skeleton-title,
70
+ .inkly-hub-skeleton-subtitle,
71
+ .inkly-hub-skeleton-card,
72
+ .inkly-hub-skeleton-line {
73
+ position: relative;
74
+ overflow: hidden;
75
+ background: #e8e8e8;
76
+ }
77
+ .inkly-hub-skeleton-brand,
78
+ .inkly-hub-skeleton-tabs,
79
+ .inkly-hub-skeleton-actions,
80
+ .inkly-hub-skeleton-title,
81
+ .inkly-hub-skeleton-subtitle,
82
+ .inkly-hub-skeleton-card,
83
+ .inkly-hub-skeleton-line {
84
+ animation: inklySkeletonPulse 1.35s ease-in-out infinite;
85
+ }
86
+ .inkly-hub-skeleton-brand {
87
+ width: 168px;
88
+ height: 30px;
89
+ border-radius: 7px;
90
+ }
91
+ .inkly-hub-skeleton-tabs {
92
+ width: min(420px, 38vw);
93
+ height: 34px;
94
+ border-radius: 8px;
95
+ }
96
+ .inkly-hub-skeleton-actions {
97
+ width: 116px;
98
+ height: 32px;
99
+ border-radius: 7px;
100
+ }
101
+ .inkly-hub-skeleton-body {
102
+ width: min(1180px, calc(100vw - 48px));
103
+ margin: 0 auto;
104
+ padding: 34px 0 48px;
105
+ }
106
+ .inkly-hub-skeleton-row {
107
+ display: flex;
108
+ align-items: flex-end;
109
+ justify-content: space-between;
110
+ gap: 24px;
111
+ margin-bottom: 26px;
112
+ }
113
+ .inkly-hub-skeleton-title {
114
+ width: min(340px, 54vw);
115
+ height: 42px;
116
+ border-radius: 8px;
117
+ }
118
+ .inkly-hub-skeleton-subtitle {
119
+ width: min(240px, 40vw);
120
+ height: 16px;
121
+ margin-top: 12px;
122
+ border-radius: 5px;
123
+ }
124
+ .inkly-hub-skeleton-count {
125
+ width: 86px;
126
+ }
127
+ .inkly-hub-skeleton-line {
128
+ height: 13px;
129
+ margin-top: 9px;
130
+ border-radius: 5px;
131
+ }
132
+ .inkly-hub-skeleton-grid {
133
+ display: grid;
134
+ grid-template-columns: repeat(3, minmax(0, 1fr));
135
+ gap: 18px;
136
+ }
137
+ .inkly-hub-skeleton-card {
138
+ aspect-ratio: 16 / 9;
139
+ border: 1px solid #dedede;
140
+ border-radius: 8px;
141
+ background: #ececec;
142
+ }
143
+ .inkly-hub-skeleton-card::after {
144
+ content: "";
145
+ position: absolute;
146
+ left: 18px;
147
+ right: 18px;
148
+ bottom: 18px;
149
+ height: 42px;
150
+ border-radius: 7px;
151
+ background: rgba(255, 255, 255, 0.58);
152
+ }
153
+ @media (max-width: 820px) {
154
+ .inkly-hub-skeleton-head {
155
+ height: auto;
156
+ align-items: stretch;
157
+ flex-direction: column;
158
+ padding: 16px;
159
+ }
160
+ .inkly-hub-skeleton-tabs,
161
+ .inkly-hub-skeleton-actions,
162
+ .inkly-hub-skeleton-brand {
163
+ width: 100%;
164
+ }
165
+ .inkly-hub-skeleton-grid {
166
+ grid-template-columns: 1fr;
167
+ }
168
+ }
169
+ @keyframes inklySkeletonPulse {
170
+ 0%, 100% { opacity: 0.62; }
171
+ 50% { opacity: 1; }
172
+ }
173
+ </style>
174
+ </head>
175
+ <body>
176
+ <div id="root">
177
+ <div class="inkly-hub-loading inkly-hub-skeleton" aria-busy="true" aria-label="Loading hub">
178
+ <div class="inkly-hub-skeleton-head">
179
+ <div class="inkly-hub-skeleton-brand"></div>
180
+ <div class="inkly-hub-skeleton-tabs"></div>
181
+ <div class="inkly-hub-skeleton-actions"></div>
182
+ </div>
183
+ <main class="inkly-hub-skeleton-body">
184
+ <div class="inkly-hub-skeleton-row">
185
+ <div>
186
+ <div class="inkly-hub-skeleton-title"></div>
187
+ <div class="inkly-hub-skeleton-subtitle"></div>
188
+ </div>
189
+ <div class="inkly-hub-skeleton-count">
190
+ <div class="inkly-hub-skeleton-line"></div>
191
+ <div class="inkly-hub-skeleton-line"></div>
192
+ </div>
193
+ </div>
194
+ <section class="inkly-hub-skeleton-grid">
195
+ <div class="inkly-hub-skeleton-card"></div>
196
+ <div class="inkly-hub-skeleton-card"></div>
197
+ <div class="inkly-hub-skeleton-card"></div>
198
+ <div class="inkly-hub-skeleton-card"></div>
199
+ <div class="inkly-hub-skeleton-card"></div>
200
+ <div class="inkly-hub-skeleton-card"></div>
201
+ </section>
202
+ </main>
203
+ </div>
204
+ </div>
205
+ <script type="module">
206
+ // Same runtime artifact the demo page loads. React is externalized
207
+ // and resolved by the import map above.
208
+ let createElement;
209
+ let createRoot;
210
+ let HubIndex;
211
+ try {
212
+ ({ createElement, createRoot, HubIndex } = await import('/__inkly/runtime.js'));
213
+ } catch (err) {
214
+ const errorEl = document.createElement('div');
215
+ errorEl.className = 'inkly-hub-loading';
216
+ const title = document.createElement('strong');
217
+ title.textContent = 'Runtime bundle missing';
218
+ const body = document.createElement('pre');
219
+ body.textContent =
220
+ 'The hub loaded, but the Inkly runtime did not.\n\n' +
221
+ 'Check /__inkly/runtime.js and /__inkly/vendor/react*.mjs, then restart inkly dev.\n\n' +
222
+ String(err && err.stack ? err.stack : err);
223
+ errorEl.append(title, document.createElement('br'), body);
224
+ const rootEl = document.getElementById('root');
225
+ if (rootEl) rootEl.replaceChildren(errorEl);
226
+ throw err;
227
+ }
228
+
229
+ const rootEl = document.getElementById('root');
230
+ const root = createRoot(rootEl);
231
+
232
+ function readInlineData() {
233
+ const node = document.getElementById('__inkly_hub_data');
234
+ if (!node) return { hub: { name: 'hub' }, demos: [] };
235
+ try { return JSON.parse(node.textContent || '{}'); } catch (e) { return { hub: { name: 'hub' }, demos: [] }; }
236
+ }
237
+
238
+ function currentCollection() {
239
+ try {
240
+ return new URLSearchParams(window.location.search).get('collection') || 'all';
241
+ } catch (e) {
242
+ return 'all';
243
+ }
244
+ }
245
+
246
+ let latestHubData = readInlineData();
247
+
248
+ function collectionKeyFromHref(href) {
249
+ try {
250
+ const url = new URL(href, window.location.origin);
251
+ if (url.origin !== window.location.origin) return null;
252
+ if (url.pathname !== '/') return null;
253
+ return url.searchParams.get('collection') || 'all';
254
+ } catch (e) {
255
+ return null;
256
+ }
257
+ }
258
+
259
+ function hrefForCollection(key) {
260
+ return key === 'all' ? '/' : '/?collection=' + encodeURIComponent(key);
261
+ }
262
+
263
+ function hrefForDemo(slug) {
264
+ return '/' + String(slug || '').split('/').map(encodeURIComponent).join('/');
265
+ }
266
+
267
+ function navigateCollection(key, mode) {
268
+ const href = hrefForCollection(key);
269
+ if (
270
+ window.location.pathname + window.location.search !== href
271
+ ) {
272
+ const fn = mode === 'replace' ? 'replaceState' : 'pushState';
273
+ window.history[fn]({ inklyCollection: key }, '', href);
274
+ }
275
+ renderHub(latestHubData, key);
276
+ }
277
+
278
+ function cliRenderLink(props) {
279
+ const children = props.children;
280
+ const attrs = Object.assign({}, props);
281
+ delete attrs.children;
282
+
283
+ const isCollectionLink = props['data-inkly-nav'] === 'collection';
284
+ if (isCollectionLink) {
285
+ attrs.onClick = function (event) {
286
+ if (
287
+ event.defaultPrevented ||
288
+ event.button !== 0 ||
289
+ event.metaKey ||
290
+ event.altKey ||
291
+ event.ctrlKey ||
292
+ event.shiftKey ||
293
+ props.target
294
+ ) {
295
+ return;
296
+ }
297
+ const key = collectionKeyFromHref(props.href);
298
+ if (!key) return;
299
+ event.preventDefault();
300
+ navigateCollection(key, 'push');
301
+ };
302
+ }
303
+ return createElement('a', attrs, children);
304
+ }
305
+
306
+ // Stylesheet element holding the active preset's hub-index CSS.
307
+ // Re-fetched whenever the active theme changes. Created once,
308
+ // mutated on subsequent renders so multiple themes can't pile up.
309
+ let themeStyleEl = null;
310
+ function applyTheme(themeId) {
311
+ if (!themeId) themeId = 'inkly';
312
+ if (themeStyleEl && themeStyleEl.dataset.themeId === themeId) return;
313
+ if (!themeStyleEl) {
314
+ themeStyleEl = document.createElement('link');
315
+ themeStyleEl.rel = 'stylesheet';
316
+ document.head.appendChild(themeStyleEl);
317
+ }
318
+ themeStyleEl.href = '/__inkly/theme/' + encodeURIComponent(themeId) + '.css';
319
+ themeStyleEl.dataset.themeId = themeId;
320
+ }
321
+
322
+
323
+ function renderHub(data, activeCollection) {
324
+ latestHubData = data;
325
+ const hubName = (data && data.hub && data.hub.name) || 'hub';
326
+ document.title = hubName + ' — inkly dev';
327
+
328
+ // Load the preset's CSS into the document. <HubIndex/> owns the
329
+ // root div, the data-layout / data-theme attrs, and the CSS
330
+ // variable application — we just supply the merged tokens and
331
+ // inject the preset CSS into <head> for the scoped selectors to
332
+ // match.
333
+ const injectedTheme = (data && data.theme) || {};
334
+ const themeId = injectedTheme.id || (data && data.hub && data.hub.theme) || 'inkly';
335
+ applyTheme(themeId);
336
+
337
+ // Merge preset defaults → hub tokens, same cascade as the hosted
338
+ // hub page. The CLI injects the current preset because the browser
339
+ // runtime bundle does not ship the server-only theme catalog.
340
+ const tokens = Object.assign(
341
+ {},
342
+ injectedTheme.tokens || {},
343
+ (data && data.hub && data.hub.tokens) || {},
344
+ );
345
+
346
+ const favicon = data && data.hub && data.hub.brand && data.hub.brand.favicon;
347
+ if (favicon) {
348
+ let link = document.querySelector('link[rel="icon"]');
349
+ if (!link) {
350
+ link = document.createElement('link');
351
+ link.rel = 'icon';
352
+ document.head.appendChild(link);
353
+ }
354
+ link.href = favicon;
355
+ }
356
+
357
+ root.render(
358
+ createElement(HubIndex, {
359
+ data,
360
+ tokens,
361
+ activeCollection: activeCollection || currentCollection(),
362
+ demoHref: hrefForDemo,
363
+ collectionHref: (key) =>
364
+ key === 'all' ? '/' : '/?collection=' + encodeURIComponent(key),
365
+ renderLink: cliRenderLink,
366
+ thumbnailThemeId: themeId,
367
+ thumbnailThemeTokens: injectedTheme.tokens || undefined,
368
+ thumbnailThemeCss: injectedTheme.css || undefined,
369
+ // CLI serves HTML snapshots at /__inkly/snapshot/<slug>/<path>;
370
+ // platform serves them at /api/demo-html/<slug>/<path>. The
371
+ // shared <HubThumbnail> uses whatever the host hands it.
372
+ resolveHtmlThumbnailSrc: (slug, snapshotPath) =>
373
+ '/__inkly/snapshot/' + String(slug || '').split('/').map(encodeURIComponent).join('/') + '/' +
374
+ snapshotPath.split('/').map(encodeURIComponent).join('/'),
375
+ }),
376
+ );
377
+ }
378
+
379
+ renderHub(latestHubData);
380
+
381
+ window.addEventListener('popstate', function () {
382
+ renderHub(latestHubData, currentCollection());
383
+ });
384
+
385
+ // HMR: re-fetch the hub payload when the dev server tells us the
386
+ // hub or any demo config changed.
387
+ async function refetchAndRender() {
388
+ try {
389
+ const [hubRes, demosRes] = await Promise.all([
390
+ fetch('/__inkly/hub', { cache: 'no-store' }),
391
+ fetch('/__inkly/demos', { cache: 'no-store' }),
392
+ ]);
393
+ if (!hubRes.ok || !demosRes.ok) return;
394
+ const hub = await hubRes.json();
395
+ const demos = await demosRes.json();
396
+ renderHub({ hub, demos, theme: latestHubData && latestHubData.theme }, currentCollection());
397
+ } catch (e) {
398
+ /* ignore — keep last render */
399
+ }
400
+ }
401
+ if (import.meta.hot) {
402
+ import.meta.hot.on('inkly:hub-changed', refetchAndRender);
403
+ }
404
+ </script>
405
+ </body>
406
+ </html>
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@inkly-org/cli",
3
+ "version": "0.5.1",
4
+ "description": "Inkly CLI — scaffold and locally preview demo configs.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "bin": {
8
+ "inkly": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "vendor",
13
+ "template",
14
+ "README.md",
15
+ "!dist/**/*.map"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "vitest run",
27
+ "prepublishOnly": "npm run build && npm run typecheck && npm run test"
28
+ },
29
+ "dependencies": {
30
+ "@opentelemetry/core": "^2.7.1",
31
+ "@opentelemetry/sdk-trace-base": "^2.7.1",
32
+ "@sentry/node": "^10.56.0",
33
+ "chokidar": "^4.0.1",
34
+ "fflate": "^0.8.3",
35
+ "mri": "^1.2.0",
36
+ "posthog-node": "^5.35.13",
37
+ "sharp": "^0.34.5",
38
+ "subset-font": "^2.5.0",
39
+ "vite": "^5.4.10",
40
+ "ws": "^8.20.0"
41
+ },
42
+ "devDependencies": {
43
+ "@inkly/demo-react": "*",
44
+ "@inkly/demo-schema": "*",
45
+ "@inkly/hub-skeleton": "*",
46
+ "@inkly/shared-html-capture": "*",
47
+ "@types/node": "^20.17.6",
48
+ "@types/ws": "^8.18.1",
49
+ "tsup": "^8.3.5",
50
+ "typescript": "^5.7.2",
51
+ "vitest": "^2.1.8"
52
+ }
53
+ }