happy-stacks 0.2.0 → 0.4.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 +84 -25
- package/bin/happys.mjs +116 -17
- package/docs/happy-development.md +2 -2
- package/docs/isolated-linux-vm.md +82 -0
- package/docs/mobile-ios.md +112 -54
- package/package.json +5 -1
- package/scripts/auth.mjs +59 -208
- package/scripts/build.mjs +58 -12
- package/scripts/cli-link.mjs +3 -3
- package/scripts/completion.mjs +5 -5
- package/scripts/daemon.mjs +168 -20
- package/scripts/dev.mjs +196 -70
- package/scripts/doctor.mjs +20 -36
- package/scripts/edison.mjs +105 -78
- package/scripts/happy.mjs +8 -19
- package/scripts/init.mjs +8 -14
- package/scripts/install.mjs +119 -23
- package/scripts/lint.mjs +31 -32
- package/scripts/menubar.mjs +6 -13
- package/scripts/migrate.mjs +11 -21
- package/scripts/mobile.mjs +93 -108
- package/scripts/mobile_dev_client.mjs +83 -0
- package/scripts/provision/linux-ubuntu-review-pr.sh +51 -0
- package/scripts/review.mjs +217 -0
- package/scripts/review_pr.mjs +368 -0
- package/scripts/run.mjs +95 -21
- package/scripts/self.mjs +11 -29
- package/scripts/server_flavor.mjs +4 -4
- package/scripts/service.mjs +19 -29
- package/scripts/setup.mjs +63 -160
- package/scripts/setup_pr.mjs +592 -52
- package/scripts/stack.mjs +608 -200
- package/scripts/stop.mjs +3 -3
- package/scripts/tailscale.mjs +44 -11
- package/scripts/test.mjs +52 -36
- package/scripts/tui.mjs +314 -74
- package/scripts/typecheck.mjs +31 -32
- package/scripts/ui_gateway.mjs +1 -1
- package/scripts/uninstall.mjs +6 -6
- package/scripts/utils/auth/daemon_gate.mjs +55 -0
- package/scripts/utils/auth/daemon_gate.test.mjs +37 -0
- package/scripts/utils/auth/dev_key.mjs +163 -0
- package/scripts/utils/{auth_files.mjs → auth/files.mjs} +2 -4
- package/scripts/utils/auth/guided_pr_auth.mjs +79 -0
- package/scripts/utils/auth/guided_stack_web_login.mjs +75 -0
- package/scripts/utils/auth/handy_master_secret.mjs +68 -0
- package/scripts/utils/auth/interactive_stack_auth.mjs +72 -0
- package/scripts/utils/{auth_login_ux.mjs → auth/login_ux.mjs} +32 -13
- package/scripts/utils/auth/sources.mjs +38 -0
- package/scripts/utils/auth/stack_guided_login.mjs +353 -0
- package/scripts/utils/cli/cli_registry.mjs +24 -0
- package/scripts/utils/cli/cwd_scope.mjs +82 -0
- package/scripts/utils/cli/cwd_scope.test.mjs +77 -0
- package/scripts/utils/cli/flags.mjs +17 -0
- package/scripts/utils/cli/log_forwarder.mjs +157 -0
- package/scripts/utils/cli/normalize.mjs +16 -0
- package/scripts/utils/cli/prereqs.mjs +72 -0
- package/scripts/utils/cli/progress.mjs +126 -0
- package/scripts/utils/cli/smoke_help.mjs +2 -2
- package/scripts/utils/cli/verbosity.mjs +12 -0
- package/scripts/utils/cli/wizard.mjs +1 -1
- package/scripts/utils/crypto/tokens.mjs +14 -0
- package/scripts/utils/{dev_daemon.mjs → dev/daemon.mjs} +51 -7
- package/scripts/utils/dev/expo_dev.mjs +246 -0
- package/scripts/utils/dev/expo_dev.test.mjs +76 -0
- package/scripts/utils/{dev_server.mjs → dev/server.mjs} +22 -32
- package/scripts/utils/dev_auth_key.mjs +1 -1
- package/scripts/utils/{config.mjs → env/config.mjs} +3 -2
- package/scripts/utils/{dotenv.mjs → env/dotenv.mjs} +3 -0
- package/scripts/utils/{env.mjs → env/env.mjs} +5 -3
- package/scripts/utils/{env_file.mjs → env/env_file.mjs} +2 -1
- package/scripts/utils/{env_local.mjs → env/env_local.mjs} +1 -0
- package/scripts/utils/env/read.mjs +30 -0
- package/scripts/utils/env/values.mjs +13 -0
- package/scripts/utils/expo/command.mjs +52 -0
- package/scripts/utils/{expo.mjs → expo/expo.mjs} +23 -10
- package/scripts/utils/expo/metro_ports.mjs +114 -0
- package/scripts/utils/fs/json.mjs +25 -0
- package/scripts/utils/fs/ops.mjs +29 -0
- package/scripts/utils/fs/package_json.mjs +8 -0
- package/scripts/utils/fs/tail.mjs +12 -0
- package/scripts/utils/git/git.mjs +67 -0
- package/scripts/utils/git/refs.mjs +26 -0
- package/scripts/utils/{worktrees.mjs → git/worktrees.mjs} +27 -23
- package/scripts/utils/handy_master_secret.mjs +2 -2
- package/scripts/utils/mobile/config.mjs +31 -0
- package/scripts/utils/mobile/dev_client_links.mjs +60 -0
- package/scripts/utils/mobile/identifiers.mjs +47 -0
- package/scripts/utils/mobile/identifiers.test.mjs +42 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +98 -0
- package/scripts/utils/net/dns.mjs +10 -0
- package/scripts/utils/net/lan_ip.mjs +24 -0
- package/scripts/utils/{ports.mjs → net/ports.mjs} +12 -6
- package/scripts/utils/net/url.mjs +30 -0
- package/scripts/utils/net/url.test.mjs +20 -0
- package/scripts/utils/paths/localhost_host.mjs +56 -0
- package/scripts/utils/{paths.mjs → paths/paths.mjs} +52 -45
- package/scripts/utils/{runtime.mjs → paths/runtime.mjs} +3 -1
- package/scripts/utils/proc/commands.mjs +34 -0
- package/scripts/utils/{ownership.mjs → proc/ownership.mjs} +1 -1
- package/scripts/utils/proc/package_scripts.mjs +31 -0
- package/scripts/utils/proc/parallel.mjs +25 -0
- package/scripts/utils/proc/pids.mjs +11 -0
- package/scripts/utils/{pm.mjs → proc/pm.mjs} +128 -158
- package/scripts/utils/{proc.mjs → proc/proc.mjs} +77 -2
- package/scripts/utils/review/base_ref.mjs +74 -0
- package/scripts/utils/review/base_ref.test.mjs +54 -0
- package/scripts/utils/review/runners/coderabbit.mjs +19 -0
- package/scripts/utils/review/runners/codex.mjs +51 -0
- package/scripts/utils/review/targets.mjs +24 -0
- package/scripts/utils/review/targets.test.mjs +36 -0
- package/scripts/utils/sandbox/review_pr_sandbox.mjs +106 -0
- package/scripts/utils/{happy_server_infra.mjs → server/infra/happy_server_infra.mjs} +10 -49
- package/scripts/utils/server/mobile_api_url.mjs +61 -0
- package/scripts/utils/server/mobile_api_url.test.mjs +41 -0
- package/scripts/utils/server/port.mjs +68 -0
- package/scripts/utils/{server.mjs → server/server.mjs} +12 -0
- package/scripts/utils/server/urls.mjs +101 -0
- package/scripts/utils/server/validate.mjs +88 -0
- package/scripts/utils/service/autostart_darwin.mjs +182 -0
- package/scripts/utils/service/autostart_darwin.test.mjs +50 -0
- package/scripts/utils/stack/context.mjs +23 -0
- package/scripts/utils/stack/dirs.mjs +27 -0
- package/scripts/utils/stack/editor_workspace.mjs +152 -0
- package/scripts/utils/stack/names.mjs +12 -0
- package/scripts/utils/stack/pr_stack_name.mjs +16 -0
- package/scripts/utils/stack/runtime_state.mjs +88 -0
- package/scripts/utils/stack/stacks.mjs +45 -0
- package/scripts/utils/{stack_startup.mjs → stack/startup.mjs} +9 -2
- package/scripts/utils/{stack_stop.mjs → stack/stop.mjs} +24 -19
- package/scripts/utils/stack_context.mjs +3 -3
- package/scripts/utils/stack_runtime_state.mjs +1 -1
- package/scripts/utils/stacks.mjs +2 -2
- package/scripts/utils/{browser.mjs → ui/browser.mjs} +1 -1
- package/scripts/utils/ui/qr.mjs +17 -0
- package/scripts/utils/ui/text.mjs +16 -0
- package/scripts/utils/validate.mjs +1 -1
- package/scripts/where.mjs +6 -6
- package/scripts/worktrees.mjs +171 -113
- package/scripts/utils/auth_sources.mjs +0 -12
- package/scripts/utils/dev_expo_web.mjs +0 -112
- package/scripts/utils/localhost_host.mjs +0 -17
- package/scripts/utils/server_port.mjs +0 -9
- package/scripts/utils/server_urls.mjs +0 -54
- /package/scripts/utils/{sandbox.mjs → env/sandbox.mjs} +0 -0
- /package/scripts/utils/{fs.mjs → fs/fs.mjs} +0 -0
- /package/scripts/utils/{canonical_home.mjs → paths/canonical_home.mjs} +0 -0
- /package/scripts/utils/{watch.mjs → proc/watch.mjs} +0 -0
package/scripts/stop.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
|
|
5
5
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
6
6
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
7
|
-
import { run, runCapture } from './utils/proc.mjs';
|
|
8
|
-
import { getRootDir, resolveStackEnvPath } from './utils/paths.mjs';
|
|
7
|
+
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
8
|
+
import { getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
9
9
|
|
|
10
10
|
function usage() {
|
|
11
11
|
return [
|
package/scripts/tailscale.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
-
import { run, runCapture } from './utils/proc.mjs';
|
|
3
|
+
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
4
4
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
5
|
-
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/sandbox.mjs';
|
|
5
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
6
|
+
import { getInternalServerUrl } from './utils/server/urls.mjs';
|
|
7
|
+
import { resolveCommandPath } from './utils/proc/commands.mjs';
|
|
6
8
|
import { constants } from 'node:fs';
|
|
7
9
|
import { access } from 'node:fs/promises';
|
|
8
10
|
|
|
@@ -21,11 +23,6 @@ import { access } from 'node:fs/promises';
|
|
|
21
23
|
* - url (print the first https:// URL from status output)
|
|
22
24
|
*/
|
|
23
25
|
|
|
24
|
-
function getInternalServerUrl() {
|
|
25
|
-
const port = process.env.HAPPY_LOCAL_SERVER_PORT?.trim() ? Number(process.env.HAPPY_LOCAL_SERVER_PORT) : 3005;
|
|
26
|
-
return `http://127.0.0.1:${port}`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
26
|
function getServeConfig(internalServerUrl) {
|
|
30
27
|
const upstream = process.env.HAPPY_LOCAL_TAILSCALE_UPSTREAM?.trim()
|
|
31
28
|
? process.env.HAPPY_LOCAL_TAILSCALE_UPSTREAM.trim()
|
|
@@ -43,7 +40,9 @@ function extractHttpsUrl(serveStatusText) {
|
|
|
43
40
|
.find((l) => l.toLowerCase().includes('https://'));
|
|
44
41
|
if (!line) return null;
|
|
45
42
|
const m = line.match(/https:\/\/\S+/i);
|
|
46
|
-
|
|
43
|
+
if (!m) return null;
|
|
44
|
+
// Avoid trailing slash for base URLs (some consumers treat it as a path prefix).
|
|
45
|
+
return m[0].replace(/\/+$/, '');
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
function tailscaleStatusMatchesInternalServerUrl(status, internalServerUrl) {
|
|
@@ -83,6 +82,16 @@ function extractServeEnableUrl(text) {
|
|
|
83
82
|
return m ? m[0] : null;
|
|
84
83
|
}
|
|
85
84
|
|
|
85
|
+
function assertTailscaleAllowed(action) {
|
|
86
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`[local] tailscale ${action} is disabled in sandbox mode.\n` +
|
|
89
|
+
`Reason: Tailscale Serve is global machine state and sandbox runs must be isolated.\n` +
|
|
90
|
+
`If you really want this, set: HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
function parseTimeoutMs(raw, defaultMs) {
|
|
87
96
|
const s = (raw ?? '').trim();
|
|
88
97
|
if (!s) return defaultMs;
|
|
@@ -134,7 +143,7 @@ async function resolveTailscaleCmd() {
|
|
|
134
143
|
|
|
135
144
|
// Try PATH first (without executing `tailscale`, which can hang in some environments).
|
|
136
145
|
try {
|
|
137
|
-
const found =
|
|
146
|
+
const found = await resolveCommandPath('tailscale', { env: tailscaleEnv(), timeoutMs: tailscaleProbeTimeoutMs() });
|
|
138
147
|
if (found) {
|
|
139
148
|
return found;
|
|
140
149
|
}
|
|
@@ -176,11 +185,13 @@ export async function tailscaleServeHttpsUrl() {
|
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
export async function tailscaleServeStatus() {
|
|
188
|
+
assertTailscaleAllowed('status');
|
|
179
189
|
const cmd = await resolveTailscaleCmd();
|
|
180
190
|
return await runCapture(cmd, ['serve', 'status'], { env: tailscaleEnv(), timeoutMs: tailscaleProbeTimeoutMs() });
|
|
181
191
|
}
|
|
182
192
|
|
|
183
193
|
export async function tailscaleServeEnable({ internalServerUrl, timeoutMs } = {}) {
|
|
194
|
+
assertTailscaleAllowed('enable');
|
|
184
195
|
const cmd = await resolveTailscaleCmd();
|
|
185
196
|
const { upstream, servePath } = getServeConfig(internalServerUrl);
|
|
186
197
|
const args = ['serve', '--bg'];
|
|
@@ -218,12 +229,16 @@ export async function tailscaleServeEnable({ internalServerUrl, timeoutMs } = {}
|
|
|
218
229
|
}
|
|
219
230
|
|
|
220
231
|
export async function tailscaleServeReset({ timeoutMs } = {}) {
|
|
232
|
+
assertTailscaleAllowed('reset');
|
|
221
233
|
const cmd = await resolveTailscaleCmd();
|
|
222
234
|
const timeout = Number.isFinite(timeoutMs) ? (timeoutMs > 0 ? timeoutMs : 0) : tailscaleUserResetTimeoutMs();
|
|
223
235
|
await run(cmd, ['serve', 'reset'], { env: tailscaleEnv(), timeoutMs: timeout });
|
|
224
236
|
}
|
|
225
237
|
|
|
226
238
|
export async function maybeEnableTailscaleServe({ internalServerUrl }) {
|
|
239
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
227
242
|
const enabled = (process.env.HAPPY_LOCAL_TAILSCALE_SERVE ?? '0') === '1';
|
|
228
243
|
if (!enabled) {
|
|
229
244
|
return null;
|
|
@@ -237,6 +252,9 @@ export async function maybeEnableTailscaleServe({ internalServerUrl }) {
|
|
|
237
252
|
}
|
|
238
253
|
|
|
239
254
|
export async function maybeResetTailscaleServe() {
|
|
255
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
240
258
|
const enabled = (process.env.HAPPY_LOCAL_TAILSCALE_SERVE ?? '0') === '1';
|
|
241
259
|
const resetOnExit = (process.env.HAPPY_LOCAL_TAILSCALE_RESET_ON_EXIT ?? '0') === '1';
|
|
242
260
|
if (!enabled || !resetOnExit) {
|
|
@@ -269,6 +287,7 @@ export async function resolvePublicServerUrl({
|
|
|
269
287
|
defaultPublicUrl,
|
|
270
288
|
envPublicUrl,
|
|
271
289
|
allowEnable = true,
|
|
290
|
+
stackName = 'main',
|
|
272
291
|
}) {
|
|
273
292
|
const preferTailscalePublicUrl = (process.env.HAPPY_LOCAL_TAILSCALE_PREFER_PUBLIC_URL ?? '1') !== '0';
|
|
274
293
|
const userExplicitlySetPublicUrl =
|
|
@@ -278,6 +297,20 @@ export async function resolvePublicServerUrl({
|
|
|
278
297
|
return { publicServerUrl: envPublicUrl || defaultPublicUrl, source: 'env' };
|
|
279
298
|
}
|
|
280
299
|
|
|
300
|
+
// Non-main stacks:
|
|
301
|
+
// - Never auto-enable (global machine state) by default.
|
|
302
|
+
// - If the caller explicitly allows it AND Tailscale Serve is already configured for this stack's
|
|
303
|
+
// internal URL, prefer the HTTPS URL (safe: status must match the internal URL).
|
|
304
|
+
if (stackName && stackName !== 'main') {
|
|
305
|
+
if (allowEnable) {
|
|
306
|
+
const existing = await tailscaleServeHttpsUrlForInternalServerUrl(internalServerUrl);
|
|
307
|
+
if (existing) {
|
|
308
|
+
return { publicServerUrl: existing, source: 'tailscale-status' };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { publicServerUrl: envPublicUrl || defaultPublicUrl, source: envPublicUrl ? 'env' : 'default' };
|
|
312
|
+
}
|
|
313
|
+
|
|
281
314
|
// If serve is already configured, use its HTTPS URL if present.
|
|
282
315
|
const existing = await tailscaleServeHttpsUrlForInternalServerUrl(internalServerUrl);
|
|
283
316
|
if (existing) {
|
|
@@ -342,7 +375,7 @@ async function main() {
|
|
|
342
375
|
return;
|
|
343
376
|
}
|
|
344
377
|
|
|
345
|
-
const internalServerUrl = getInternalServerUrl();
|
|
378
|
+
const internalServerUrl = getInternalServerUrl({ env: process.env, defaultPort: 3005 }).internalServerUrl;
|
|
346
379
|
if (flags.has('--upstream') || kv.get('--upstream')) {
|
|
347
380
|
process.env.HAPPY_LOCAL_TAILSCALE_UPSTREAM = kv.get('--upstream') ?? internalServerUrl;
|
|
348
381
|
}
|
package/scripts/test.mjs
CHANGED
|
@@ -1,36 +1,18 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
3
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
4
|
-
import { getComponentDir, getRootDir } from './utils/paths.mjs';
|
|
5
|
-
import { ensureDepsInstalled
|
|
6
|
-
import { pathExists } from './utils/fs.mjs';
|
|
7
|
-
import { run } from './utils/proc.mjs';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
4
|
+
import { componentDirEnvKey, getComponentDir, getRootDir } from './utils/paths/paths.mjs';
|
|
5
|
+
import { ensureDepsInstalled } from './utils/proc/pm.mjs';
|
|
6
|
+
import { pathExists } from './utils/fs/fs.mjs';
|
|
7
|
+
import { run } from './utils/proc/proc.mjs';
|
|
8
|
+
import { detectPackageManagerCmd, pickFirstScript, readPackageJsonScripts } from './utils/proc/package_scripts.mjs';
|
|
9
|
+
import { getInvokedCwd, inferComponentFromCwd } from './utils/cli/cwd_scope.mjs';
|
|
10
10
|
|
|
11
11
|
const DEFAULT_COMPONENTS = ['happy', 'happy-cli', 'happy-server-light', 'happy-server'];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (await pathExists(join(dir, 'yarn.lock'))) {
|
|
15
|
-
return { name: 'yarn', cmd: 'yarn', argsForScript: (script) => ['-s', script] };
|
|
16
|
-
}
|
|
17
|
-
await requirePnpm();
|
|
18
|
-
return { name: 'pnpm', cmd: 'pnpm', argsForScript: (script) => ['--silent', script] };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function readScripts(dir) {
|
|
22
|
-
try {
|
|
23
|
-
const raw = await readFile(join(dir, 'package.json'), 'utf-8');
|
|
24
|
-
const pkg = JSON.parse(raw);
|
|
25
|
-
const scripts = pkg?.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {};
|
|
26
|
-
return scripts;
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
12
|
+
const EXTRA_COMPONENTS = ['stacks'];
|
|
13
|
+
const VALID_COMPONENTS = [...DEFAULT_COMPONENTS, ...EXTRA_COMPONENTS];
|
|
31
14
|
|
|
32
15
|
function pickTestScript(scripts) {
|
|
33
|
-
if (!scripts) return null;
|
|
34
16
|
const candidates = [
|
|
35
17
|
'test',
|
|
36
18
|
'tst',
|
|
@@ -38,7 +20,7 @@ function pickTestScript(scripts) {
|
|
|
38
20
|
'test:unit',
|
|
39
21
|
'check:test',
|
|
40
22
|
];
|
|
41
|
-
return
|
|
23
|
+
return pickFirstScript(scripts, candidates);
|
|
42
24
|
}
|
|
43
25
|
|
|
44
26
|
async function main() {
|
|
@@ -49,33 +31,67 @@ async function main() {
|
|
|
49
31
|
if (wantsHelp(argv, { flags })) {
|
|
50
32
|
printResult({
|
|
51
33
|
json,
|
|
52
|
-
data: { components:
|
|
34
|
+
data: { components: VALID_COMPONENTS, flags: ['--json'] },
|
|
53
35
|
text: [
|
|
54
36
|
'[test] usage:',
|
|
55
37
|
' happys test [component...] [--json]',
|
|
56
38
|
'',
|
|
57
39
|
'components:',
|
|
58
|
-
` ${
|
|
40
|
+
` ${VALID_COMPONENTS.join(' | ')}`,
|
|
59
41
|
'',
|
|
60
42
|
'examples:',
|
|
61
43
|
' happys test',
|
|
44
|
+
' happys test stacks',
|
|
62
45
|
' happys test happy happy-cli',
|
|
46
|
+
'',
|
|
47
|
+
'note:',
|
|
48
|
+
' If run from inside a component checkout/worktree and no components are provided, defaults to that component.',
|
|
63
49
|
].join('\n'),
|
|
64
50
|
});
|
|
65
51
|
return;
|
|
66
52
|
}
|
|
67
53
|
|
|
54
|
+
const rootDir = getRootDir(import.meta.url);
|
|
55
|
+
|
|
68
56
|
const positionals = argv.filter((a) => !a.startsWith('--'));
|
|
69
|
-
const
|
|
57
|
+
const inferred =
|
|
58
|
+
positionals.length === 0
|
|
59
|
+
? inferComponentFromCwd({
|
|
60
|
+
rootDir,
|
|
61
|
+
invokedCwd: getInvokedCwd(process.env),
|
|
62
|
+
components: DEFAULT_COMPONENTS,
|
|
63
|
+
})
|
|
64
|
+
: null;
|
|
65
|
+
if (inferred) {
|
|
66
|
+
const stacksKey = componentDirEnvKey(inferred.component);
|
|
67
|
+
const legacyKey = stacksKey.replace(/^HAPPY_STACKS_/, 'HAPPY_LOCAL_');
|
|
68
|
+
if (!(process.env[stacksKey] ?? '').toString().trim() && !(process.env[legacyKey] ?? '').toString().trim()) {
|
|
69
|
+
process.env[stacksKey] = inferred.repoDir;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const requested = positionals.length ? positionals : inferred ? [inferred.component] : ['all'];
|
|
70
74
|
const wantAll = requested.includes('all');
|
|
75
|
+
// Default `all` excludes "stacks" to avoid coupling to component repos and their test baselines.
|
|
71
76
|
const components = wantAll ? DEFAULT_COMPONENTS : requested;
|
|
72
77
|
|
|
73
|
-
const rootDir = getRootDir(import.meta.url);
|
|
74
|
-
|
|
75
78
|
const results = [];
|
|
76
79
|
for (const component of components) {
|
|
77
|
-
if (!
|
|
78
|
-
results.push({ component, ok: false, skipped: false, error: `unknown component (expected one of: ${
|
|
80
|
+
if (!VALID_COMPONENTS.includes(component)) {
|
|
81
|
+
results.push({ component, ok: false, skipped: false, error: `unknown component (expected one of: ${VALID_COMPONENTS.join(', ')})` });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (component === 'stacks') {
|
|
86
|
+
try {
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.log('[test] stacks: running node --test (happy-stacks unit tests)');
|
|
89
|
+
// Restrict to explicit *.test.mjs files to avoid accidentally executing scripts/test.mjs.
|
|
90
|
+
await run('sh', ['-lc', 'node --test "scripts/**/*.test.mjs"'], { cwd: rootDir, env: process.env });
|
|
91
|
+
results.push({ component, ok: true, skipped: false, dir: rootDir, pm: 'node', script: '--test' });
|
|
92
|
+
} catch (e) {
|
|
93
|
+
results.push({ component, ok: false, skipped: false, dir: rootDir, pm: 'node', script: '--test', error: String(e?.message ?? e) });
|
|
94
|
+
}
|
|
79
95
|
continue;
|
|
80
96
|
}
|
|
81
97
|
|
|
@@ -85,7 +101,7 @@ async function main() {
|
|
|
85
101
|
continue;
|
|
86
102
|
}
|
|
87
103
|
|
|
88
|
-
const scripts = await
|
|
104
|
+
const scripts = await readPackageJsonScripts(dir);
|
|
89
105
|
if (!scripts) {
|
|
90
106
|
results.push({ component, ok: true, skipped: true, dir, reason: 'no package.json' });
|
|
91
107
|
continue;
|