anhedral 0.4.1 → 0.6.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 +29 -256
- package/assets/anhedral-cli-init-technical.svg +322 -0
- package/assets/anhedral-cli-init.svg +257 -173
- package/dist/bin.js +28 -162
- package/dist/dependencies.d.ts +7 -56
- package/dist/dependencies.js +96 -305
- package/dist/index.d.ts +2 -7
- package/dist/index.js +1 -4
- package/dist/{templates → platforms}/desktop.d.ts +1 -1
- package/dist/{templates → platforms}/desktop.js +28 -266
- package/dist/{templates → platforms}/extension.d.ts +1 -1
- package/dist/platforms/extension.js +401 -0
- package/dist/{templates → platforms}/mobile.d.ts +1 -1
- package/dist/platforms/mobile.js +232 -0
- package/dist/project.d.ts +4 -0
- package/dist/project.js +1 -0
- package/dist/standard-products.d.ts +2 -0
- package/dist/standard-products.js +6 -0
- package/dist/standard.d.ts +18 -0
- package/dist/standard.js +578 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/application-stack-standard.md +700 -0
- package/package.json +34 -49
- package/CHANGELOG.md +0 -132
- package/CONTRIBUTING.md +0 -73
- package/SECURITY.md +0 -37
- package/SKILL.md +0 -244
- package/assets/images/svg/ably-symbol-color.svg +0 -8
- package/bin/anhedral.js +0 -2
- package/dist/architecture/contributions.d.ts +0 -28
- package/dist/architecture/contributions.js +0 -125
- package/dist/architecture/index.d.ts +0 -12
- package/dist/architecture/index.js +0 -6
- package/dist/architecture/manifest.d.ts +0 -53
- package/dist/architecture/manifest.js +0 -283
- package/dist/architecture/modules.d.ts +0 -37
- package/dist/architecture/modules.js +0 -184
- package/dist/architecture/plan.d.ts +0 -58
- package/dist/architecture/plan.js +0 -145
- package/dist/architecture/templates.d.ts +0 -10
- package/dist/architecture/templates.js +0 -23
- package/dist/cli.d.ts +0 -32
- package/dist/cli.js +0 -235
- package/dist/composers/root.d.ts +0 -6
- package/dist/composers/root.js +0 -35
- package/dist/print.d.ts +0 -8
- package/dist/print.js +0 -48
- package/dist/prompts.d.ts +0 -5
- package/dist/prompts.js +0 -19
- package/dist/scaffold.d.ts +0 -76
- package/dist/scaffold.js +0 -1895
- package/dist/template-source.d.ts +0 -20
- package/dist/template-source.js +0 -180
- package/dist/templates/api.d.ts +0 -2
- package/dist/templates/api.js +0 -3122
- package/dist/templates/assets-private-proxy.d.ts +0 -3
- package/dist/templates/assets-private-proxy.js +0 -277
- package/dist/templates/electron-updater.d.ts +0 -3
- package/dist/templates/electron-updater.js +0 -358
- package/dist/templates/extension.js +0 -732
- package/dist/templates/mobile.js +0 -720
- package/dist/templates/shared.d.ts +0 -2
- package/dist/templates/shared.js +0 -520
- package/dist/templates/web.d.ts +0 -2
- package/dist/templates/web.js +0 -412
- package/dist/toolchain.d.ts +0 -3
- package/dist/toolchain.js +0 -8
- package/dist/ui.d.ts +0 -33
- package/dist/ui.js +0 -199
- package/dist/workspace-config.d.ts +0 -11
- package/dist/workspace-config.js +0 -372
- package/docs/conventions.md +0 -92
- package/docs/master-stack-map.md +0 -199
- package/docs/output-tree-contract.md +0 -482
- package/docs/references/manual-scaffolding.md +0 -403
- package/docs/references/provisioning.md +0 -220
- package/scripts/render-master-stack-map.mjs +0 -510
- package/templates/api-fastify/apps/api/tsconfig.json +0 -20
- package/templates/catalog.json +0 -23
- package/templates/db-drizzle/packages/db/tsconfig.json +0 -14
- package/templates/desktop-electron/apps/desktop/tsconfig.json +0 -37
- package/templates/extension-wxt/apps/extension/tsconfig.json +0 -41
- package/templates/mobile-expo/apps/mobile/expo-env.d.ts +0 -1
- package/templates/web-next/apps/web/next-env.d.ts +0 -6
|
@@ -1,48 +1,21 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { writeFile } from '../util.js';
|
|
3
|
-
import {
|
|
4
|
-
import { DESKTOP_DEPENDENCIES, ELECTRON_UPDATER_DEPENDENCIES } from '../dependencies.js';
|
|
3
|
+
import { DESKTOP_DEPENDENCIES } from '../dependencies.js';
|
|
5
4
|
import { childPackageName, htmlText, identifierSegment, jsString } from '../render.js';
|
|
6
|
-
function selectedDependencies(options) {
|
|
7
|
-
const dependencies = { ...(DESKTOP_DEPENDENCIES.dependencies ?? {}) };
|
|
8
|
-
if (!options.apps.api)
|
|
9
|
-
delete dependencies['@shared/api-client'];
|
|
10
|
-
if (!options.features.billing)
|
|
11
|
-
delete dependencies['@shared/realtime'];
|
|
12
|
-
if (!options.features.auth) {
|
|
13
|
-
delete dependencies['@clerk/clerk-js'];
|
|
14
|
-
delete dependencies['@clerk/ui'];
|
|
15
|
-
delete dependencies['@solana/web3.js'];
|
|
16
|
-
}
|
|
17
|
-
if (options.features.electronUpdater) {
|
|
18
|
-
Object.assign(dependencies, ELECTRON_UPDATER_DEPENDENCIES.dependencies);
|
|
19
|
-
}
|
|
20
|
-
return dependencies;
|
|
21
|
-
}
|
|
22
5
|
export async function scaffoldDesktop(root, options) {
|
|
23
6
|
const { projectName, displayName } = options;
|
|
24
7
|
const dir = path.join(root, 'apps/desktop');
|
|
25
|
-
|
|
26
|
-
anhedralPrint.step('Writing Electron desktop app');
|
|
27
|
-
writePackageJson(dir, projectName, options);
|
|
8
|
+
writePackageJson(dir, projectName);
|
|
28
9
|
writeTsConfig(dir);
|
|
29
10
|
writeViteConfig(dir);
|
|
30
11
|
writePostcssConfig(dir);
|
|
31
12
|
writeDevScript(dir);
|
|
32
13
|
writeShadcnConfig(dir);
|
|
33
|
-
writeEnvExample(dir
|
|
34
|
-
writeSourceFiles(dir, displayName
|
|
35
|
-
anhedralPrint.done('Electron desktop app written');
|
|
14
|
+
writeEnvExample(dir);
|
|
15
|
+
writeSourceFiles(dir, displayName);
|
|
36
16
|
}
|
|
37
|
-
function writePackageJson(dir, projectName
|
|
38
|
-
const updatePublish =
|
|
39
|
-
artifactName: '${productName}-${version}-${os}-${arch}.${ext}',
|
|
40
|
-
publish: [{
|
|
41
|
-
provider: 'generic',
|
|
42
|
-
url: '${env.DESKTOP_UPDATE_BASE_URL}/releases/${os}/${arch}',
|
|
43
|
-
useMultipleRangeRequest: false,
|
|
44
|
-
}],
|
|
45
|
-
} : {};
|
|
17
|
+
function writePackageJson(dir, projectName) {
|
|
18
|
+
const updatePublish = {};
|
|
46
19
|
writeFile(path.join(dir, 'package.json'), JSON.stringify({
|
|
47
20
|
name: childPackageName(projectName, 'desktop'),
|
|
48
21
|
version: '0.1.0',
|
|
@@ -53,15 +26,11 @@ function writePackageJson(dir, projectName, options) {
|
|
|
53
26
|
dev: 'tsc -p tsconfig.main.json && node scripts/dev.mjs',
|
|
54
27
|
build: 'tsc --noEmit && tsc -p tsconfig.main.json && vite build',
|
|
55
28
|
typecheck: 'tsc --noEmit',
|
|
56
|
-
'build:mac': `pnpm build && electron-builder --mac${
|
|
57
|
-
'build:win': `pnpm build && electron-builder --win${
|
|
58
|
-
'build:linux': `pnpm build && electron-builder --linux${
|
|
59
|
-
package: `pnpm build && electron-builder${
|
|
60
|
-
...(
|
|
61
|
-
'updates:build:mac': 'pnpm build && electron-builder --mac --publish never',
|
|
62
|
-
'updates:build:win': 'pnpm build && electron-builder --win --publish never',
|
|
63
|
-
'updates:build:linux': 'pnpm build && electron-builder --linux --publish never',
|
|
64
|
-
} : {}),
|
|
29
|
+
'build:mac': `pnpm build && electron-builder --mac${''}`,
|
|
30
|
+
'build:win': `pnpm build && electron-builder --win${''}`,
|
|
31
|
+
'build:linux': `pnpm build && electron-builder --linux${''}`,
|
|
32
|
+
package: `pnpm build && electron-builder${''}`,
|
|
33
|
+
...({}),
|
|
65
34
|
},
|
|
66
35
|
build: {
|
|
67
36
|
appId: `dev.anhedral.${identifierSegment(projectName)}`,
|
|
@@ -85,7 +54,7 @@ function writePackageJson(dir, projectName, options) {
|
|
|
85
54
|
},
|
|
86
55
|
...updatePublish,
|
|
87
56
|
},
|
|
88
|
-
dependencies:
|
|
57
|
+
dependencies: DESKTOP_DEPENDENCIES.dependencies,
|
|
89
58
|
devDependencies: DESKTOP_DEPENDENCIES.devDependencies,
|
|
90
59
|
}, null, 2) + '\n');
|
|
91
60
|
}
|
|
@@ -121,6 +90,9 @@ export default defineConfig({
|
|
|
121
90
|
build: {
|
|
122
91
|
outDir: 'dist/renderer',
|
|
123
92
|
emptyOutDir: true,
|
|
93
|
+
// Clerk is already loaded behind a dynamic import. Its optional account-management
|
|
94
|
+
// surface is intentionally a large async chunk and does not inflate initial rendering.
|
|
95
|
+
chunkSizeWarningLimit: 1600,
|
|
124
96
|
},
|
|
125
97
|
});
|
|
126
98
|
`);
|
|
@@ -237,62 +209,12 @@ function writeShadcnConfig(dir) {
|
|
|
237
209
|
iconLibrary: 'lucide',
|
|
238
210
|
}, null, 2) + '\n');
|
|
239
211
|
}
|
|
240
|
-
function writeEnvExample(dir
|
|
241
|
-
|
|
242
|
-
options.apps.api ? 'VITE_API_URL=http://localhost:8787/api' : null,
|
|
243
|
-
options.features.auth ? 'VITE_CLERK_PUBLISHABLE_KEY=pk_test_***' : null,
|
|
244
|
-
].filter((value) => value !== null);
|
|
245
|
-
writeFile(path.join(dir, '.env.example'), lines.join('\n') + (lines.length > 0 ? '\n' : ''));
|
|
246
|
-
if (options.features.electronUpdater) {
|
|
247
|
-
writeFile(path.join(dir, 'electron-builder.env.example'), 'DESKTOP_UPDATE_BASE_URL=https://updates.example.com\n');
|
|
248
|
-
}
|
|
212
|
+
function writeEnvExample(dir) {
|
|
213
|
+
writeFile(path.join(dir, '.env.example'), '');
|
|
249
214
|
}
|
|
250
|
-
function writeSourceFiles(dir, displayName
|
|
215
|
+
function writeSourceFiles(dir, displayName) {
|
|
251
216
|
const displayNameLiteral = jsString(displayName);
|
|
252
217
|
const displayNameHtml = htmlText(displayName);
|
|
253
|
-
const description = options.apps.api
|
|
254
|
-
? 'Electron + shadcn/ui desktop client using the same shared API client as web, mobile, and extension.'
|
|
255
|
-
: 'Electron + shadcn/ui desktop client ready for your application.';
|
|
256
|
-
const buttonLabel = options.features.auth ? 'Open account' : options.apps.api ? 'Explore API' : 'Get started';
|
|
257
|
-
const updaterImport = options.features.electronUpdater
|
|
258
|
-
? "import electronUpdater, { type AppUpdater } from 'electron-updater';\n"
|
|
259
|
-
: '';
|
|
260
|
-
const updaterRuntime = options.features.electronUpdater ? `
|
|
261
|
-
const UPDATE_CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000;
|
|
262
|
-
let updateTimer: NodeJS.Timeout | undefined;
|
|
263
|
-
|
|
264
|
-
function getAutoUpdater(): AppUpdater {
|
|
265
|
-
const { autoUpdater } = electronUpdater;
|
|
266
|
-
return autoUpdater;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async function checkForDesktopUpdate(updater: AppUpdater): Promise<void> {
|
|
270
|
-
try {
|
|
271
|
-
await updater.checkForUpdatesAndNotify();
|
|
272
|
-
} catch (error) {
|
|
273
|
-
console.error('Desktop update check failed:', error);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function startDesktopUpdates(): void {
|
|
278
|
-
if (!app.isPackaged) return;
|
|
279
|
-
const updater = getAutoUpdater();
|
|
280
|
-
updater.autoDownload = true;
|
|
281
|
-
updater.autoInstallOnAppQuit = true;
|
|
282
|
-
updater.on('error', (error) => {
|
|
283
|
-
console.error('Desktop updater error:', error);
|
|
284
|
-
});
|
|
285
|
-
void checkForDesktopUpdate(updater);
|
|
286
|
-
updateTimer = setInterval(() => {
|
|
287
|
-
void checkForDesktopUpdate(updater);
|
|
288
|
-
}, UPDATE_CHECK_INTERVAL_MS);
|
|
289
|
-
updateTimer.unref();
|
|
290
|
-
}
|
|
291
|
-
` : '';
|
|
292
|
-
const updaterStart = options.features.electronUpdater ? ' startDesktopUpdates();\n' : '';
|
|
293
|
-
const updaterStop = options.features.electronUpdater
|
|
294
|
-
? "app.on('before-quit', () => { if (updateTimer) clearInterval(updateTimer); });\n"
|
|
295
|
-
: '';
|
|
296
218
|
writeFile(path.join(dir, 'src/main/app-window.ts'), `import { BrowserWindow, shell } from 'electron';
|
|
297
219
|
import path from 'node:path';
|
|
298
220
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -366,20 +288,20 @@ export function createAppWindow(): BrowserWindow {
|
|
|
366
288
|
`);
|
|
367
289
|
writeFile(path.join(dir, 'src/main/main.ts'), `import { app, BrowserWindow, session } from 'electron';
|
|
368
290
|
import { createAppWindow } from './app-window.js';
|
|
369
|
-
|
|
291
|
+
|
|
370
292
|
|
|
371
293
|
app.whenReady().then(() => {
|
|
372
294
|
session.defaultSession.setPermissionCheckHandler(() => false);
|
|
373
295
|
session.defaultSession.setPermissionRequestHandler((_webContents, _permission, callback) => callback(false));
|
|
374
296
|
createAppWindow();
|
|
375
|
-
|
|
297
|
+
});
|
|
376
298
|
app.on('window-all-closed', () => {
|
|
377
299
|
if (process.platform !== 'darwin') app.quit();
|
|
378
300
|
});
|
|
379
301
|
app.on('activate', () => {
|
|
380
302
|
if (BrowserWindow.getAllWindows().length === 0) createAppWindow();
|
|
381
303
|
});
|
|
382
|
-
|
|
304
|
+
`);
|
|
383
305
|
writeFile(path.join(dir, 'src/main/preload.cts'), `import { contextBridge } from 'electron';
|
|
384
306
|
|
|
385
307
|
contextBridge.exposeInMainWorld('anhedral', {
|
|
@@ -407,130 +329,6 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
407
329
|
return twMerge(clsx(inputs));
|
|
408
330
|
}
|
|
409
331
|
`);
|
|
410
|
-
if (options.apps.api) {
|
|
411
|
-
const authImport = options.features.auth ? "import { getAuthToken } from './auth';\n" : '';
|
|
412
|
-
const getToken = options.features.auth ? 'getAuthToken' : 'async () => null';
|
|
413
|
-
writeFile(path.join(dir, 'src/renderer/lib/api.ts'), `import { ApiClient, normalizeApiBaseUrl } from '@shared/api-client';
|
|
414
|
-
${authImport}
|
|
415
|
-
|
|
416
|
-
function apiBaseUrl(): string {
|
|
417
|
-
const configured = import.meta.env.VITE_API_URL?.trim();
|
|
418
|
-
const candidate = configured || (import.meta.env.DEV ? 'http://localhost:8787/api' : '');
|
|
419
|
-
if (!candidate) throw new Error('VITE_API_URL is required in production builds');
|
|
420
|
-
const normalized = normalizeApiBaseUrl(candidate, 'VITE_API_URL');
|
|
421
|
-
const url = new URL(normalized);
|
|
422
|
-
if (import.meta.env.PROD && url.protocol !== 'https:') {
|
|
423
|
-
throw new Error('VITE_API_URL must use https: in production');
|
|
424
|
-
}
|
|
425
|
-
return normalized;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
export const api = new ApiClient({
|
|
429
|
-
baseUrl: apiBaseUrl(),
|
|
430
|
-
getToken: ${getToken},
|
|
431
|
-
});
|
|
432
|
-
`);
|
|
433
|
-
}
|
|
434
|
-
if (options.features.auth) {
|
|
435
|
-
writeFile(path.join(dir, 'src/renderer/lib/auth.ts'), `import type { Clerk as ClerkInstance } from '@clerk/clerk-js';
|
|
436
|
-
|
|
437
|
-
const publishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY || '';
|
|
438
|
-
let initialization: Promise<ClerkInstance | null> | null = null;
|
|
439
|
-
|
|
440
|
-
export function isClerkConfigured(): boolean {
|
|
441
|
-
return publishableKey.length > 0;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
export function initializeClerk(): Promise<ClerkInstance | null> {
|
|
445
|
-
if (!publishableKey) return Promise.resolve(null);
|
|
446
|
-
if (!initialization) {
|
|
447
|
-
initialization = Promise.all([
|
|
448
|
-
import('@clerk/clerk-js'),
|
|
449
|
-
import('@clerk/ui'),
|
|
450
|
-
]).then(async ([{ Clerk }, { ui }]) => {
|
|
451
|
-
const clerk = new Clerk(publishableKey);
|
|
452
|
-
await clerk.load({ ui });
|
|
453
|
-
return clerk;
|
|
454
|
-
}).catch((error) => {
|
|
455
|
-
initialization = null;
|
|
456
|
-
throw error;
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
return initialization;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
export async function getAuthToken(): Promise<string | null> {
|
|
463
|
-
const clerk = await initializeClerk();
|
|
464
|
-
return clerk?.session?.getToken() ?? null;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export async function getAuthUserId(): Promise<string | null> {
|
|
468
|
-
const clerk = await initializeClerk();
|
|
469
|
-
return clerk?.user?.id ?? null;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
export async function openAccount(): Promise<boolean> {
|
|
473
|
-
const clerk = await initializeClerk();
|
|
474
|
-
if (!clerk) return false;
|
|
475
|
-
|
|
476
|
-
if (clerk.user) clerk.openUserProfile();
|
|
477
|
-
else clerk.openSignIn();
|
|
478
|
-
return true;
|
|
479
|
-
}
|
|
480
|
-
`);
|
|
481
|
-
}
|
|
482
|
-
if (options.features.billing) {
|
|
483
|
-
writeFile(path.join(dir, 'src/renderer/hooks/use-entitlement.ts'), `import { subscribeToSubscriptionChanges } from '@shared/realtime';
|
|
484
|
-
import * as React from 'react';
|
|
485
|
-
import { api } from '../lib/api';
|
|
486
|
-
import { getAuthUserId, initializeClerk } from '../lib/auth';
|
|
487
|
-
|
|
488
|
-
export function useEntitlement(enabled: boolean) {
|
|
489
|
-
const [userId, setUserId] = React.useState<string | null>(null);
|
|
490
|
-
const [entitlement, setEntitlement] = React.useState<Awaited<ReturnType<typeof api.getEntitlement>> | null>(null);
|
|
491
|
-
const [error, setError] = React.useState<string | null>(null);
|
|
492
|
-
const revision = React.useRef(0);
|
|
493
|
-
const refresh = React.useCallback(async () => {
|
|
494
|
-
const currentUserId = await getAuthUserId();
|
|
495
|
-
setUserId(currentUserId);
|
|
496
|
-
if (!currentUserId) return;
|
|
497
|
-
try {
|
|
498
|
-
const next = await api.getEntitlement();
|
|
499
|
-
revision.current = Math.max(revision.current, next.revision);
|
|
500
|
-
setEntitlement(next);
|
|
501
|
-
setError(null);
|
|
502
|
-
} catch (cause) {
|
|
503
|
-
setError(cause instanceof Error ? cause.message : 'Unable to load subscription');
|
|
504
|
-
}
|
|
505
|
-
}, []);
|
|
506
|
-
React.useEffect(() => {
|
|
507
|
-
if (!enabled) return;
|
|
508
|
-
let unsubscribe: (() => void) | undefined;
|
|
509
|
-
void initializeClerk().then((clerk) => {
|
|
510
|
-
unsubscribe = clerk?.addListener(() => { void refresh(); });
|
|
511
|
-
void refresh();
|
|
512
|
-
});
|
|
513
|
-
return () => unsubscribe?.();
|
|
514
|
-
}, [enabled, refresh]);
|
|
515
|
-
React.useEffect(() => {
|
|
516
|
-
if (!enabled) return;
|
|
517
|
-
const onVisibility = () => { if (document.visibilityState === 'visible') void refresh(); };
|
|
518
|
-
document.addEventListener('visibilitychange', onVisibility);
|
|
519
|
-
return () => document.removeEventListener('visibilitychange', onVisibility);
|
|
520
|
-
}, [enabled, refresh]);
|
|
521
|
-
React.useEffect(() => {
|
|
522
|
-
if (!enabled || !userId) return;
|
|
523
|
-
return subscribeToSubscriptionChanges({
|
|
524
|
-
userId,
|
|
525
|
-
getTokenRequest: () => api.getRealtimeToken(),
|
|
526
|
-
onChange: (nextRevision) => { if (nextRevision > revision.current) void refresh(); },
|
|
527
|
-
onError: (cause) => setError(cause.message),
|
|
528
|
-
});
|
|
529
|
-
}, [enabled, refresh, userId]);
|
|
530
|
-
return { entitlement, error };
|
|
531
|
-
}
|
|
532
|
-
`);
|
|
533
|
-
}
|
|
534
332
|
writeFile(path.join(dir, 'src/renderer/components/ui/button.tsx'), `import * as React from 'react';
|
|
535
333
|
import { cn } from '@/lib/utils';
|
|
536
334
|
|
|
@@ -544,59 +342,23 @@ export function Button({ className, type = 'button', ...props }: React.ButtonHTM
|
|
|
544
342
|
);
|
|
545
343
|
}
|
|
546
344
|
`);
|
|
547
|
-
const authImport = options.features.auth ? "import { isClerkConfigured, openAccount } from '@/lib/auth';\n" : '';
|
|
548
|
-
const entitlementImport = options.features.billing ? "import { useEntitlement } from '@/hooks/use-entitlement';\n" : '';
|
|
549
|
-
const entitlementState = options.features.billing ? " const { entitlement, error: entitlementError } = useEntitlement(clerkState === 'ready');\n" : '';
|
|
550
|
-
const entitlementStatus = options.features.billing ? ` {entitlement ? <p className="text-muted-foreground">Plan: {entitlement.entitlement} ({entitlement.status})</p> : null}
|
|
551
|
-
{entitlementError ? <p role="alert" className="text-red-700">{entitlementError}</p> : null}
|
|
552
|
-
` : '';
|
|
553
|
-
const authState = options.features.auth ? ` const [clerkState, setClerkState] = React.useState<'unconfigured' | 'idle' | 'loading' | 'ready' | 'error'>(
|
|
554
|
-
() => isClerkConfigured() ? 'idle' : 'unconfigured',
|
|
555
|
-
);
|
|
556
|
-
|
|
557
|
-
const handleAccount = async () => {
|
|
558
|
-
setClerkState('loading');
|
|
559
|
-
try {
|
|
560
|
-
const opened = await openAccount();
|
|
561
|
-
setClerkState(opened ? 'ready' : 'unconfigured');
|
|
562
|
-
} catch {
|
|
563
|
-
setClerkState('error');
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
` : '';
|
|
568
|
-
const authStatus = options.features.auth ? ` <p
|
|
569
|
-
role={clerkState === 'error' || clerkState === 'unconfigured' ? 'alert' : 'status'}
|
|
570
|
-
aria-live={clerkState === 'error' || clerkState === 'unconfigured' ? 'assertive' : 'polite'}
|
|
571
|
-
className={clerkState === 'error' || clerkState === 'unconfigured' ? 'text-red-700' : 'text-muted-foreground'}
|
|
572
|
-
>
|
|
573
|
-
{clerkState === 'unconfigured' ? 'Set VITE_CLERK_PUBLISHABLE_KEY to enable accounts.' : null}
|
|
574
|
-
{clerkState === 'idle' ? 'Account services load only when you open your account.' : null}
|
|
575
|
-
{clerkState === 'loading' ? 'Loading account services…' : null}
|
|
576
|
-
{clerkState === 'ready' ? 'Account services are ready.' : null}
|
|
577
|
-
{clerkState === 'error' ? 'Clerk could not initialize. Check the publishable key and network connection.' : null}
|
|
578
|
-
</p>
|
|
579
|
-
` : '';
|
|
580
|
-
const buttonAction = options.features.auth
|
|
581
|
-
? " disabled={clerkState === 'loading' || clerkState === 'unconfigured'} onClick={() => void handleAccount()}"
|
|
582
|
-
: '';
|
|
583
345
|
writeFile(path.join(dir, 'src/renderer/main.tsx'), `import React from 'react';
|
|
584
346
|
import ReactDOM from 'react-dom/client';
|
|
585
|
-
import
|
|
586
|
-
${authImport}${entitlementImport}import './styles.css';
|
|
347
|
+
import './styles.css';
|
|
587
348
|
|
|
588
349
|
function App() {
|
|
589
|
-
|
|
350
|
+
return (
|
|
590
351
|
<main className="flex min-h-screen flex-col gap-6 bg-background p-8 text-foreground">
|
|
591
352
|
<section className="flex flex-col gap-3">
|
|
592
353
|
<h1 className="text-3xl font-semibold">{${displayNameLiteral}}</h1>
|
|
593
354
|
<p className="max-w-2xl text-muted-foreground">
|
|
594
|
-
${
|
|
355
|
+
${"Electron + shadcn/ui desktop client ready for your application."}
|
|
595
356
|
</p>
|
|
596
357
|
</section>
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
|
|
600
362
|
</main>
|
|
601
363
|
);
|
|
602
364
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { ProjectOptions } from '../
|
|
1
|
+
import type { ProjectOptions } from '../project.js';
|
|
2
2
|
export declare function scaffoldExtension(root: string, options: ProjectOptions): Promise<void>;
|