anhedral 0.1.0 → 0.1.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.
@@ -9,7 +9,7 @@ export async function scaffoldFrontend(root, { projectName, displayName }) {
9
9
  // ── 1. Scaffold via react-native-reusables CLI ────────────────────────
10
10
  console.log(' Scaffolding Expo app with react-native-reusables...');
11
11
  fs.mkdirSync(dir, { recursive: true });
12
- execExpect(`pnpm dlx ${toolPackageRef('@react-native-reusables/cli', toolchain.reactNativeReusables)} init -t clerk-auth`, dir, [
12
+ await execExpect(`pnpm dlx ${toolPackageRef('@react-native-reusables/cli', toolchain.reactNativeReusables)} init -t clerk-auth`, dir, [
13
13
  ['What is the name of your project?', projectName],
14
14
  ['Would you like to install dependencies?', 'n'],
15
15
  ['Would you like to initialize a Git repository?', 'n'],
package/dist/toolchain.js CHANGED
@@ -11,11 +11,17 @@ const TOOLCHAIN_SPECS = {
11
11
  },
12
12
  stable: {
13
13
  verifiedAt: '2026-03-22',
14
+ // renovate: datasource=npm depName=create-vite
14
15
  viteCreate: '7.1.4',
16
+ // renovate: datasource=npm depName=shadcn
15
17
  shadcn: '4.1.0',
18
+ // renovate: datasource=npm depName=@react-native-reusables/cli
16
19
  reactNativeReusables: '0.5.0',
20
+ // renovate: datasource=npm depName=wxt
17
21
  wxt: '0.20.20',
22
+ // renovate: datasource=npm depName=@tauri-apps/cli
18
23
  tauriCli: '2.8.4',
24
+ // renovate: datasource=npm depName=@tauri-apps/api
19
25
  tauriApi: '2.8.0',
20
26
  },
21
27
  };
package/dist/util.d.ts CHANGED
@@ -3,4 +3,4 @@ export declare function appendGitignore(root: string, lines: string[]): void;
3
3
  export declare function exec(cmd: string, cwd: string): void;
4
4
  export declare function execWithInput(cmd: string, cwd: string, stdinInput: string): void;
5
5
  export declare function liftNestedProject(root: string, nestedName: string): void;
6
- export declare function execExpect(cmd: string, cwd: string, prompts: [waitFor: string, answer: string][]): void;
6
+ 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 } from 'node:child_process';
2
+ import { execSync, spawn } 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 });
@@ -56,44 +56,68 @@ export function liftNestedProject(root, nestedName) {
56
56
  renameSync(tempSelfNamedChild, path.join(root, nestedName));
57
57
  }
58
58
  }
59
- function bashSingleQuoteEscape(value) {
60
- return `'${value.replace(/'/g, `'\\''`)}'`;
61
- }
62
- function tclDoubleQuoteEscape(value) {
63
- return value
64
- .replace(/\\/g, '\\\\')
65
- .replace(/"/g, '\\"')
66
- .replace(/\[/g, '\\[')
67
- .replace(/\]/g, '\\]')
68
- .replace(/\$/g, '\\$')
69
- .replace(/\r/g, '\\r')
70
- .replace(/\n/g, '\\n');
71
- }
72
- /**
73
- * Escape a string for use inside a Tcl double-quoted `send` argument.
74
- * Converts JS escape sequences to their Tcl equivalents.
75
- */
76
- function tclSendEscape(s) {
77
- return tclDoubleQuoteEscape(s).replace(/\x1b/g, '\\033');
59
+ function trimOutputTail(output, maxLength = 4000) {
60
+ if (output.length <= maxLength) {
61
+ return output;
62
+ }
63
+ return output.slice(output.length - maxLength);
78
64
  }
79
- export function execExpect(cmd, cwd, prompts) {
65
+ export async function execExpect(cmd, cwd, prompts, timeoutMs = 120_000) {
80
66
  console.log(` $ ${cmd}`);
81
- const shellCommand = `cd ${bashSingleQuoteEscape(cwd)} && ${cmd}`;
82
- const sendLines = prompts
83
- .map(([wait, answer]) => `expect "${wait}"\nsend "${tclSendEscape(answer)}\\r"`)
84
- .join('\n');
85
- const expectScript = [
86
- `set timeout 120`,
87
- `set shell_command "${tclDoubleQuoteEscape(shellCommand)}"`,
88
- `spawn bash -lc $shell_command`,
89
- sendLines,
90
- `expect eof`,
91
- `catch wait result`,
92
- `exit [lindex $result 3]`,
93
- ].join('\n');
94
- execSync(`expect << 'EXPECT_EOF'\n${expectScript}\nEXPECT_EOF`, {
95
- cwd,
96
- stdio: 'inherit',
97
- shell: '/bin/bash',
67
+ await new Promise((resolve, reject) => {
68
+ const child = spawn('bash', ['-lc', cmd], {
69
+ cwd,
70
+ env: process.env,
71
+ stdio: ['pipe', 'pipe', 'pipe'],
72
+ });
73
+ let combinedOutput = '';
74
+ let promptIndex = 0;
75
+ let settled = false;
76
+ const finish = (handler) => {
77
+ if (settled) {
78
+ return;
79
+ }
80
+ settled = true;
81
+ clearTimeout(timeoutId);
82
+ handler();
83
+ };
84
+ const maybeAnswerPrompt = () => {
85
+ while (promptIndex < prompts.length && combinedOutput.includes(prompts[promptIndex][0])) {
86
+ child.stdin.write(`${prompts[promptIndex][1]}\n`);
87
+ promptIndex += 1;
88
+ }
89
+ };
90
+ const handleChunk = (chunk, target) => {
91
+ const text = chunk.toString();
92
+ combinedOutput += text;
93
+ target.write(text);
94
+ maybeAnswerPrompt();
95
+ };
96
+ const timeoutId = setTimeout(() => {
97
+ child.kill('SIGTERM');
98
+ finish(() => {
99
+ reject(new Error(`Command timed out after ${timeoutMs}ms: ${cmd}\n` +
100
+ `Answered ${promptIndex}/${prompts.length} prompts.\n` +
101
+ `Recent output:\n${trimOutputTail(combinedOutput)}`));
102
+ });
103
+ }, timeoutMs);
104
+ child.stdout.on('data', (chunk) => handleChunk(chunk, process.stdout));
105
+ child.stderr.on('data', (chunk) => handleChunk(chunk, process.stderr));
106
+ child.on('error', (error) => {
107
+ finish(() => {
108
+ reject(error);
109
+ });
110
+ });
111
+ child.on('close', (code) => {
112
+ finish(() => {
113
+ if (code === 0) {
114
+ resolve();
115
+ return;
116
+ }
117
+ reject(new Error(`Command failed with exit code ${code}: ${cmd}\n` +
118
+ `Answered ${promptIndex}/${prompts.length} prompts.\n` +
119
+ `Recent output:\n${trimOutputTail(combinedOutput)}`));
120
+ });
121
+ });
98
122
  });
99
123
  }
package/favicon.ico ADDED
Binary file
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "anhedral",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Opinionated init CLI for Next, Next + Fastify, Expo + Fastify, and backend starters with Neon, Drizzle, R2, auth, and billing integrations.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "anhedral": "./bin/anhedral.js"
7
+ "anhedral": "bin/anhedral.js"
8
8
  },
9
9
  "files": [
10
10
  "bin",
11
11
  "dist",
12
12
  "LICENSE",
13
13
  "README.md",
14
- "anhedral.svg"
14
+ "anhedral.svg",
15
+ "favicon.ico"
15
16
  ],
16
17
  "repository": {
17
18
  "type": "git",
@@ -21,6 +22,10 @@
21
22
  "bugs": {
22
23
  "url": "https://github.com/anhedral/anhedral-init/issues"
23
24
  },
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "provenance": true
28
+ },
24
29
  "engines": {
25
30
  "node": ">=20.0.0"
26
31
  },
@@ -36,7 +41,10 @@
36
41
  "test:cli:validation": "node scripts/test-cli-validation.js",
37
42
  "test:cli:scaffold": "node scripts/test-cli-scaffold-headless.js",
38
43
  "test:e2e": "node scripts/test-e2e-public-stacks.js",
39
- "test:all": "pnpm test && pnpm test:e2e"
44
+ "test:e2e:latest": "node scripts/test-e2e-public-stacks.js --toolchain latest",
45
+ "test:all": "pnpm test && pnpm test:e2e",
46
+ "release:check": "pnpm test:all && npm pack --dry-run",
47
+ "demo:refresh": "pnpm build && node scripts/refresh-demo.js"
40
48
  },
41
49
  "keywords": [
42
50
  "cli",