anhedral 0.1.1 → 0.1.4

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.
@@ -1,26 +1,32 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { writeAnhedralLogo } from '../branding.js';
4
+ import { anhedralPrint } from '../print.js';
4
5
  import { appendGitignore, writeFile, exec, execExpect, liftNestedProject } from '../util.js';
6
+ import { FRONTEND_ADDON_DEPENDENCIES, withVersions } from '../dependencies.js';
5
7
  import { resolveToolchainChannel, resolveToolchain, toolPackageRef } from '../toolchain.js';
6
- export async function scaffoldFrontend(root, { projectName, displayName }) {
7
- const dir = path.join(root, 'frontend');
8
+ export async function scaffoldFrontend(root, { projectName, displayName, skipInstall }) {
9
+ const dir = path.join(root, 'apps/frontend');
8
10
  const toolchain = resolveToolchain(resolveToolchainChannel(process.env.ANHEDRAL_TOOLCHAIN));
9
- // ── 1. Scaffold via react-native-reusables CLI ────────────────────────
10
- console.log(' Scaffolding Expo app with react-native-reusables...');
11
+ anhedralPrint.section('Frontend (Expo)');
12
+ anhedralPrint.step('Scaffolding Expo app with react-native-reusables');
11
13
  fs.mkdirSync(dir, { recursive: true });
12
14
  await execExpect(`pnpm dlx ${toolPackageRef('@react-native-reusables/cli', toolchain.reactNativeReusables)} init -t clerk-auth`, dir, [
13
15
  ['What is the name of your project?', projectName],
14
16
  ['Would you like to install dependencies?', 'n'],
15
17
  ['Would you like to initialize a Git repository?', 'n'],
16
18
  ]);
17
- // The CLI scaffolds into a subdir named after the project — move contents up
18
19
  liftNestedProject(dir, projectName);
19
- console.log(' Installing frontend dependencies...');
20
- exec('pnpm install', dir);
20
+ anhedralPrint.done('Expo app scaffolded');
21
+ anhedralPrint.step('Installing frontend dependencies');
21
22
  patchFrontendPackageJson(dir);
23
+ patchExpoAppConfig(dir);
24
+ if (!skipInstall) {
25
+ exec('pnpm install --no-frozen-lockfile', dir);
26
+ exec('pnpm exec expo install --fix --pnpm', dir);
27
+ }
22
28
  appendGitignore(dir, ['.env', '.env.*', '!.env.example']);
23
- // ── 2. Patch in API client ────────────────────────────────────────────
29
+ anhedralPrint.done(skipInstall ? 'Frontend dependency manifests written' : 'Frontend dependencies installed');
24
30
  writeApiClient(dir);
25
31
  writeConfigFile(dir);
26
32
  writeApiHook(dir);
@@ -29,190 +35,81 @@ export async function scaffoldFrontend(root, { projectName, displayName }) {
29
35
  writeAppShellFiles(dir, displayName);
30
36
  writeEnvExample(dir);
31
37
  writeEnvFile(dir);
38
+ writeVercelConfig(dir);
32
39
  writeAnhedralLogo(dir);
33
- // ── 2b. Write subscription files ─────────────────────────────────────
34
40
  writeSubscriptionProvider(dir);
35
41
  writeUseSubscription(dir);
36
- // ── 3. Install extra dependencies ─────────────────────────────────────
37
- console.log(' Installing additional dependencies...');
38
- exec('pnpm exec expo install expo-image-picker', dir);
39
- exec('pnpm add react-native-purchases react-native-purchases-ui @revenuecat/purchases-js', dir);
42
+ anhedralPrint.step('Installing Expo native packages');
43
+ if (skipInstall) {
44
+ anhedralPrint.info('Skipping Expo native package install (--skip-install)');
45
+ anhedralPrint.info('Run after init: pnpm --filter ./apps/frontend exec expo install --pnpm expo-image-picker');
46
+ }
47
+ else {
48
+ exec('pnpm exec expo install --pnpm expo-image-picker', dir);
49
+ exec('pnpm exec expo install --fix --pnpm', dir);
50
+ }
51
+ anhedralPrint.done(skipInstall ? 'Expo native package install skipped' : 'Expo native packages installed');
52
+ anhedralPrint.step('Installing RevenueCat SDKs');
53
+ const revenueCatDependencies = withVersions({
54
+ 'react-native-purchases': FRONTEND_ADDON_DEPENDENCIES['react-native-purchases'],
55
+ 'react-native-purchases-ui': FRONTEND_ADDON_DEPENDENCIES['react-native-purchases-ui'],
56
+ '@revenuecat/purchases-js': FRONTEND_ADDON_DEPENDENCIES['@revenuecat/purchases-js'],
57
+ });
58
+ if (skipInstall) {
59
+ anhedralPrint.info(`Skipping RevenueCat SDK install (--skip-install). Run after init: pnpm --filter ./apps/frontend add ${revenueCatDependencies.join(' ')}`);
60
+ }
61
+ else {
62
+ exec(`pnpm add ${revenueCatDependencies.join(' ')}`, dir);
63
+ }
64
+ anhedralPrint.done(skipInstall ? 'RevenueCat SDK install skipped' : 'Additional Expo SDKs installed');
40
65
  }
41
66
  function patchFrontendPackageJson(dir) {
42
67
  const filePath = path.join(dir, 'package.json');
43
68
  const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
69
+ packageJson.dependencies = {
70
+ ...(packageJson.dependencies ?? {}),
71
+ '@anhedral/api-client': FRONTEND_ADDON_DEPENDENCIES['@anhedral/api-client'],
72
+ '@clerk/clerk-expo': FRONTEND_ADDON_DEPENDENCIES['@clerk/clerk-expo'],
73
+ 'react-native-purchases': FRONTEND_ADDON_DEPENDENCIES['react-native-purchases'],
74
+ 'react-native-purchases-ui': FRONTEND_ADDON_DEPENDENCIES['react-native-purchases-ui'],
75
+ '@revenuecat/purchases-js': FRONTEND_ADDON_DEPENDENCIES['@revenuecat/purchases-js'],
76
+ };
44
77
  packageJson.scripts = {
45
78
  ...(packageJson.scripts ?? {}),
79
+ build: 'pnpm typecheck && pnpm build:web',
80
+ 'build:web': 'expo export --platform web',
46
81
  typecheck: 'tsc --noEmit',
47
82
  };
48
83
  writeFile(filePath, JSON.stringify(packageJson, null, 2) + '\n');
49
84
  }
50
- function writeApiClient(dir) {
51
- writeFile(path.join(dir, 'api/client.ts'), `/**
52
- * API Client with SWR caching
53
- */
54
-
55
- const SWR_MAX_SIZE = 200;
56
- const SWR_DEFAULT_STALE_MS = 15_000;
57
- const SWR_DEFAULT_MAX_AGE_MS = 300_000;
58
-
59
- type SWREntry<T> = { data: T; fetchedAt: number; staleMs: number; maxAgeMs: number };
60
- const SWR_CACHE = new Map<string, SWREntry<unknown>>();
61
- const SWR_INFLIGHT = new Map<string, Promise<unknown>>();
62
-
63
- function swrEvict() {
64
- if (SWR_CACHE.size <= SWR_MAX_SIZE) return;
65
- const entries = [...SWR_CACHE.entries()].sort((a, b) => a[1].fetchedAt - b[1].fetchedAt);
66
- const toRemove = entries.slice(0, entries.length - SWR_MAX_SIZE);
67
- for (const [key] of toRemove) SWR_CACHE.delete(key);
68
- }
69
-
70
- export class APIRequestError extends Error {
71
- constructor(
72
- public statusCode: number,
73
- message: string,
74
- public errorCode?: string,
75
- ) {
76
- super(message);
77
- this.name = 'APIRequestError';
78
- }
79
- }
80
-
81
- export class APIClient {
82
- constructor(
83
- private baseUrl: string,
84
- private getToken: () => Promise<string | null>,
85
- ) {}
86
-
87
- private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
88
- const token = await this.getToken();
89
- const headers: Record<string, string> = {
90
- 'Content-Type': 'application/json',
91
- 'X-Platform': 'web',
92
- };
93
- if (token) headers['Authorization'] = \`Bearer \${token}\`;
94
-
95
- const res = await fetch(\`\${this.baseUrl}\${path}\`, {
96
- method,
97
- headers,
98
- body: body ? JSON.stringify(body) : undefined,
99
- });
100
-
101
- if (!res.ok) {
102
- let err: { error?: string; message?: string } = {};
103
- try { err = await res.json(); } catch {}
104
- throw new APIRequestError(res.status, err.message || \`Request failed (\${res.status})\`, err.error);
105
- }
106
-
107
- if (res.status === 204) return {} as T;
108
- return res.json();
109
- }
110
-
111
- async get<T>(path: string): Promise<T> { return this.request<T>('GET', path); }
112
- async post<T>(path: string, body?: unknown): Promise<T> { return this.request<T>('POST', path, body); }
113
- async delete<T>(path: string): Promise<T> { return this.request<T>('DELETE', path); }
114
-
115
- async getCached<T>(
116
- path: string,
117
- opts?: { staleMs?: number; maxAgeMs?: number },
118
- ): Promise<T> {
119
- const staleMs = opts?.staleMs ?? SWR_DEFAULT_STALE_MS;
120
- const maxAgeMs = opts?.maxAgeMs ?? SWR_DEFAULT_MAX_AGE_MS;
121
- const now = Date.now();
122
- const cached = SWR_CACHE.get(path) as SWREntry<T> | undefined;
123
-
124
- if (cached) {
125
- const age = now - cached.fetchedAt;
126
- if (age < staleMs) return cached.data;
127
- if (age < maxAgeMs) {
128
- if (!SWR_INFLIGHT.has(path)) {
129
- const p = this.get<T>(path).then((data) => {
130
- SWR_CACHE.set(path, { data, fetchedAt: Date.now(), staleMs, maxAgeMs });
131
- return data;
132
- }).finally(() => SWR_INFLIGHT.delete(path));
133
- SWR_INFLIGHT.set(path, p);
134
- }
135
- return cached.data;
136
- }
137
- }
138
-
139
- const inflight = SWR_INFLIGHT.get(path) as Promise<T> | undefined;
140
- if (inflight) return inflight;
141
-
142
- const p = this.get<T>(path).then((data) => {
143
- SWR_CACHE.set(path, { data, fetchedAt: Date.now(), staleMs, maxAgeMs });
144
- swrEvict();
145
- return data;
146
- }).finally(() => SWR_INFLIGHT.delete(path));
147
- SWR_INFLIGHT.set(path, p);
148
- return p;
149
- }
150
-
151
- invalidateCache(path: string): void { SWR_CACHE.delete(path); }
152
- invalidateCachePrefix(prefix: string): void {
153
- for (const key of SWR_CACHE.keys()) {
154
- if (key.startsWith(prefix)) SWR_CACHE.delete(key);
85
+ function patchExpoAppConfig(dir) {
86
+ const filePath = path.join(dir, 'app.json');
87
+ if (!fs.existsSync(filePath)) {
88
+ return;
155
89
  }
156
- }
157
-
158
- clearAllCaches(): void {
159
- SWR_CACHE.clear();
160
- SWR_INFLIGHT.clear();
161
- }
162
-
163
- // ── Auth endpoints ──────────────────────────────────────────────────
164
- async getMe() {
165
- return this.get<{
166
- user: {
167
- id: string;
168
- email: string;
169
- firstName?: string | null;
170
- lastName?: string | null;
171
- displayName: string;
172
- imageUrl?: string | null;
173
- avatarUrl?: string | null;
174
- creditsBalance: number;
175
- subscriptionTier: string;
176
- subscriptionStatus: string;
177
- };
178
- }>('/auth/me');
179
- }
180
-
181
- async signOut() {
182
- return this.post<{ success: boolean }>('/auth/signout');
183
- }
184
-
185
- async uploadAvatar(input: { base64: string; mimeType: string; fileName?: string }) {
186
- return this.post<{ ok: boolean; avatarUrl: string }>('/auth/avatar', input);
187
- }
188
-
189
- async deleteAccount() {
190
- return this.delete<void>('/auth/account');
191
- }
192
-
193
- // ── Subscription endpoints ───────────────────────────────────────────
194
- async getSubscriptionPricing() {
195
- return this.getCached<{
196
- tiers: { tier: string; displayName: string; description: string; priceMonthly: number | null; priceYearly: number | null; currency: string; limits: { dailyLimit: number | null }; paymentInfo?: { revenueCatEntitlementId: string; revenueCatOfferingId: string } }[];
197
- }>('/subscriptions/pricing', { staleMs: 300_000, maxAgeMs: 3_600_000 });
198
- }
90
+ const appJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
91
+ appJson.expo ??= {};
92
+ appJson.expo.web = {
93
+ ...(appJson.expo.web ?? {}),
94
+ bundler: 'metro',
95
+ output: 'static',
96
+ };
97
+ writeFile(filePath, JSON.stringify(appJson, null, 2) + '\n');
98
+ }
99
+ function writeApiClient(dir) {
100
+ writeFile(path.join(dir, 'api/client.ts'), `import { ApiClient, APIRequestError } from '@anhedral/api-client';
199
101
 
200
- async getSubscriptionEntitlements(options?: { refresh?: boolean }) {
201
- const path = options?.refresh ? '/subscriptions/entitlements/me?refresh=true' : '/subscriptions/entitlements/me';
202
- return this.get<{
203
- pro: boolean; inTrial: boolean; trialEndsAt?: string; expiresAt?: string;
204
- periodStart?: string; periodEnd?: string;
205
- method?: 'trialing' | 'redeemed' | 'paid' | null;
206
- managementUrl?: string; cancelAtPeriodEnd?: boolean;
207
- }>(path);
102
+ export class APIClient extends ApiClient {
103
+ constructor(baseUrl: string, getToken: () => Promise<string | null>) {
104
+ super({ baseUrl, getToken, platform: 'frontend' });
208
105
  }
209
106
 
210
- async redeemPromoCode(code: string) {
211
- return this.post<{ ok: boolean; expiresAt: string }>('/subscriptions/redeem', { code });
107
+ redeemPromoCode(code: string) {
108
+ return this.redeemCode(code);
212
109
  }
213
110
  }
214
111
 
215
- export { SWR_CACHE };
112
+ export { APIRequestError };
216
113
  `);
217
114
  writeFile(path.join(dir, 'api/index.ts'), `export { APIClient, APIRequestError } from './client';
218
115
  `);
@@ -283,6 +180,22 @@ export function useAPI() {
283
180
  }
284
181
  `);
285
182
  }
183
+ function writeVercelConfig(dir) {
184
+ writeFile(path.join(dir, 'vercel.json'), JSON.stringify({
185
+ $schema: 'https://openapi.vercel.sh/vercel.json',
186
+ buildCommand: 'pnpm build:web',
187
+ outputDirectory: 'dist',
188
+ devCommand: 'pnpm dev',
189
+ cleanUrls: true,
190
+ framework: null,
191
+ rewrites: [
192
+ {
193
+ source: '/:path*',
194
+ destination: '/index.html',
195
+ },
196
+ ],
197
+ }, null, 2) + '\n');
198
+ }
286
199
  function writeAccountHook(dir) {
287
200
  writeFile(path.join(dir, 'hooks/useAccount.ts'), `import { useAuth } from '@clerk/clerk-expo';
288
201
  import * as React from 'react';
@@ -858,7 +771,7 @@ import { ScrollView, View } from 'react-native';
858
771
  const FEATURES = [
859
772
  {
860
773
  title: 'Landing page here',
861
- description: 'Replace this placeholder copy with the actual acquisition story for your mobile and web app.',
774
+ description: 'Replace this placeholder copy with the actual acquisition story for your Expo app.',
862
775
  icon: CloudIcon,
863
776
  },
864
777
  {
@@ -2,12 +2,9 @@ export declare const TOOLCHAIN_CHANNELS: readonly ["latest", "stable"];
2
2
  export type ToolchainChannel = (typeof TOOLCHAIN_CHANNELS)[number];
3
3
  export type ToolchainSpec = {
4
4
  verifiedAt: string | null;
5
- viteCreate: string;
6
5
  shadcn: string;
7
6
  reactNativeReusables: string;
8
7
  wxt: string;
9
- tauriCli: string;
10
- tauriApi: string;
11
8
  };
12
9
  export declare function resolveToolchainChannel(value: string | undefined): ToolchainChannel;
13
10
  export declare function resolveToolchain(channel: ToolchainChannel): ToolchainSpec;
package/dist/toolchain.js CHANGED
@@ -1,28 +1,17 @@
1
+ import { TOOLCHAIN_DEPENDENCIES, VERIFIED_AT } from './dependencies.js';
1
2
  export const TOOLCHAIN_CHANNELS = ['latest', 'stable'];
2
3
  const TOOLCHAIN_SPECS = {
3
4
  latest: {
4
5
  verifiedAt: null,
5
- viteCreate: 'latest',
6
6
  shadcn: 'latest',
7
7
  reactNativeReusables: 'latest',
8
8
  wxt: 'latest',
9
- tauriCli: 'latest',
10
- tauriApi: 'latest',
11
9
  },
12
10
  stable: {
13
- verifiedAt: '2026-03-22',
14
- // renovate: datasource=npm depName=create-vite
15
- viteCreate: '7.1.4',
16
- // renovate: datasource=npm depName=shadcn
17
- shadcn: '4.1.0',
18
- // renovate: datasource=npm depName=@react-native-reusables/cli
19
- reactNativeReusables: '0.5.0',
20
- // renovate: datasource=npm depName=wxt
21
- wxt: '0.20.20',
22
- // renovate: datasource=npm depName=@tauri-apps/cli
23
- tauriCli: '2.8.4',
24
- // renovate: datasource=npm depName=@tauri-apps/api
25
- tauriApi: '2.8.0',
11
+ verifiedAt: VERIFIED_AT,
12
+ shadcn: TOOLCHAIN_DEPENDENCIES.shadcn,
13
+ reactNativeReusables: TOOLCHAIN_DEPENDENCIES.reactNativeReusables,
14
+ wxt: TOOLCHAIN_DEPENDENCIES.wxt,
26
15
  },
27
16
  };
28
17
  export function resolveToolchainChannel(value) {
package/dist/util.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export declare function writeFile(filePath: string, content: string): void;
2
2
  export declare function appendGitignore(root: string, lines: string[]): void;
3
3
  export declare function exec(cmd: string, cwd: string): void;
4
+ export declare function execCommand(command: string, args: string[], cwd: string): void;
4
5
  export declare function execWithInput(cmd: string, cwd: string, stdinInput: string): void;
5
6
  export declare function liftNestedProject(root: string, nestedName: string): void;
6
7
  export declare function execExpect(cmd: string, cwd: string, prompts: [waitFor: string, answer: string][], timeoutMs?: number): Promise<void>;
package/dist/util.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
- import { execSync, spawn } from 'node:child_process';
2
+ import { execSync, spawn, spawnSync } from 'node:child_process';
3
3
  import path from 'node:path';
4
4
  export function writeFile(filePath, content) {
5
5
  mkdirSync(path.dirname(filePath), { recursive: true });
@@ -13,13 +13,66 @@ export function appendGitignore(root, lines) {
13
13
  const merged = [...new Set([...existing, ...lines])];
14
14
  writeFile(filePath, merged.join('\n') + '\n');
15
15
  }
16
+ const VERBOSE = process.env.ANHEDRAL_VERBOSE === '1';
17
+ function dumpFailure(error, cmd) {
18
+ const err = error;
19
+ const stdoutText = err.stdout ? err.stdout.toString() : '';
20
+ const stderrText = err.stderr ? err.stderr.toString() : '';
21
+ if (stdoutText)
22
+ process.stderr.write(stdoutText);
23
+ if (stderrText)
24
+ process.stderr.write(stderrText);
25
+ throw new Error(`Command failed (exit ${err.status ?? '?'}): ${cmd}`);
26
+ }
27
+ function commandLabel(command, args) {
28
+ return [command, ...args].join(' ');
29
+ }
16
30
  export function exec(cmd, cwd) {
17
- console.log(` $ ${cmd}`);
18
- execSync(cmd, { cwd, stdio: 'inherit' });
31
+ if (VERBOSE) {
32
+ console.log(` $ ${cmd}`);
33
+ execSync(cmd, { cwd, stdio: 'inherit' });
34
+ return;
35
+ }
36
+ try {
37
+ execSync(cmd, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
38
+ }
39
+ catch (error) {
40
+ dumpFailure(error, cmd);
41
+ }
42
+ }
43
+ export function execCommand(command, args, cwd) {
44
+ const label = commandLabel(command, args);
45
+ if (VERBOSE) {
46
+ console.log(` $ ${label}`);
47
+ }
48
+ const result = spawnSync(command, args, {
49
+ cwd,
50
+ encoding: 'utf8',
51
+ stdio: VERBOSE ? 'inherit' : ['ignore', 'pipe', 'pipe'],
52
+ });
53
+ if (result.status === 0) {
54
+ return;
55
+ }
56
+ if (!VERBOSE) {
57
+ if (result.stdout)
58
+ process.stderr.write(result.stdout);
59
+ if (result.stderr)
60
+ process.stderr.write(result.stderr);
61
+ }
62
+ throw new Error(`Command failed (exit ${result.status ?? '?'}): ${label}`);
19
63
  }
20
64
  export function execWithInput(cmd, cwd, stdinInput) {
21
- console.log(` $ ${cmd}`);
22
- execSync(cmd, { cwd, stdio: ['pipe', 'inherit', 'inherit'], input: stdinInput });
65
+ if (VERBOSE) {
66
+ console.log(` $ ${cmd}`);
67
+ execSync(cmd, { cwd, stdio: ['pipe', 'inherit', 'inherit'], input: stdinInput });
68
+ return;
69
+ }
70
+ try {
71
+ execSync(cmd, { cwd, stdio: ['pipe', 'pipe', 'pipe'], input: stdinInput });
72
+ }
73
+ catch (error) {
74
+ dumpFailure(error, cmd);
75
+ }
23
76
  }
24
77
  function moveDirectoryContents(sourceDir, targetDir) {
25
78
  for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
@@ -63,11 +116,14 @@ function trimOutputTail(output, maxLength = 4000) {
63
116
  return output.slice(output.length - maxLength);
64
117
  }
65
118
  export async function execExpect(cmd, cwd, prompts, timeoutMs = 120_000) {
66
- console.log(` $ ${cmd}`);
119
+ if (VERBOSE) {
120
+ console.log(` $ ${cmd}`);
121
+ }
67
122
  await new Promise((resolve, reject) => {
68
- const child = spawn('bash', ['-lc', cmd], {
123
+ const child = spawn(cmd, {
69
124
  cwd,
70
125
  env: process.env,
126
+ shell: true,
71
127
  stdio: ['pipe', 'pipe', 'pipe'],
72
128
  });
73
129
  let combinedOutput = '';
@@ -90,7 +146,8 @@ export async function execExpect(cmd, cwd, prompts, timeoutMs = 120_000) {
90
146
  const handleChunk = (chunk, target) => {
91
147
  const text = chunk.toString();
92
148
  combinedOutput += text;
93
- target.write(text);
149
+ if (VERBOSE)
150
+ target.write(text);
94
151
  maybeAnswerPrompt();
95
152
  };
96
153
  const timeoutId = setTimeout(() => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "anhedral",
3
- "version": "0.1.1",
4
- "description": "Opinionated init CLI for Next, Next + Fastify, Expo + Fastify, and backend starters with Neon, Drizzle, R2, auth, and billing integrations.",
3
+ "version": "0.1.4",
4
+ "description": "Opinionated init CLI for Expo, Fastify, WXT, shared packages, Neon, Drizzle, Clerk, RevenueCat, Stripe, R2, and Vercel.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "anhedral": "bin/anhedral.js"
@@ -23,8 +23,7 @@
23
23
  "url": "https://github.com/anhedral/anhedral-init/issues"
24
24
  },
25
25
  "publishConfig": {
26
- "access": "public",
27
- "provenance": true
26
+ "access": "public"
28
27
  },
29
28
  "engines": {
30
29
  "node": ">=20.0.0"
@@ -37,14 +36,14 @@
37
36
  "test": "pnpm test:cli",
38
37
  "pretest": "pnpm build",
39
38
  "prepublishOnly": "pnpm test:all",
40
- "test:cli": "node scripts/test-cli.js",
41
- "test:cli:validation": "node scripts/test-cli-validation.js",
42
- "test:cli:scaffold": "node scripts/test-cli-scaffold-headless.js",
43
- "test:e2e": "node scripts/test-e2e-public-stacks.js",
44
- "test:e2e:latest": "node scripts/test-e2e-public-stacks.js --toolchain latest",
45
- "test:all": "pnpm test && pnpm test:e2e",
39
+ "test:cli": "node tests/test-cli.js",
40
+ "test:cli:validation": "node tests/test-cli-validation.js",
41
+ "test:deps": "pnpm build && node tests/test-dependency-manifest.js",
42
+ "test:e2e": "node tests/test-e2e-public-stacks.js",
43
+ "test:e2e:latest": "node tests/test-e2e-public-stacks.js --toolchain latest",
44
+ "test:all": "pnpm test && pnpm test:deps && pnpm test:e2e",
46
45
  "release:check": "pnpm test:all && npm pack --dry-run",
47
- "demo:refresh": "pnpm build && node scripts/refresh-demo.js"
46
+ "demo:refresh": "pnpm build && node tests/refresh-demo.js"
48
47
  },
49
48
  "keywords": [
50
49
  "cli",
@@ -55,8 +54,8 @@
55
54
  "drizzle",
56
55
  "neondb",
57
56
  "revenuecat",
58
- "better-auth",
59
- "shadcn"
57
+ "wxt",
58
+ "vercel"
60
59
  ],
61
60
  "license": "MIT",
62
61
  "packageManager": "pnpm@10.15.1",
@@ -1,2 +0,0 @@
1
- export declare function writeDefaultWebEnvExample(root: string): void;
2
- export declare function writeDefaultWebFiles(root: string, displayName: string): void;