@happier-dev/stack 0.1.0-preview.142.1 → 0.1.0-preview.21.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.
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.d.ts +4 -0
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.d.ts.map +1 -1
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.js +21 -1
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.js.map +1 -1
- package/package.json +1 -1
- package/scripts/auth_login_guided_server_no_expo.test.mjs +2 -0
- package/scripts/bundleWorkspaceDeps.test.mjs +26 -1
- package/scripts/mobile.mjs +4 -7
- package/scripts/mobile_dev_client.mjs +7 -32
- package/scripts/mobile_dev_client_help_smoke.test.mjs +24 -0
- package/scripts/mobile_run_ios_passes_port.integration.test.mjs +8 -6
- package/scripts/mobile_run_ios_uses_long_port_flag.test.mjs +106 -0
- package/scripts/remote_cmd.mjs +112 -0
- package/scripts/self_host_runtime.mjs +505 -94
- package/scripts/self_host_runtime.test.mjs +170 -8
- package/scripts/stack/help_text.mjs +1 -1
- package/scripts/stack.mjs +2 -1
- package/scripts/stack_pr_help_cmd.test.mjs +38 -0
- package/scripts/stop.mjs +2 -3
- package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +64 -3
- package/scripts/utils/cli/cli_registry.mjs +5 -2
- package/scripts/utils/mobile/dev_client_install_invocation.mjs +68 -0
- package/scripts/utils/mobile/dev_client_install_invocation.test.mjs +27 -0
|
@@ -11,5 +11,9 @@ export declare function buildLaunchdPlistXml(params: Readonly<{
|
|
|
11
11
|
workingDirectory?: string;
|
|
12
12
|
keepAliveOnFailure?: boolean;
|
|
13
13
|
startIntervalSec?: number;
|
|
14
|
+
startCalendarInterval?: Readonly<{
|
|
15
|
+
hour: number;
|
|
16
|
+
minute: number;
|
|
17
|
+
}>;
|
|
14
18
|
}>): string;
|
|
15
19
|
//# sourceMappingURL=launchd.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launchd.d.ts","sourceRoot":"","sources":["../../src/service/launchd.ts"],"names":[],"mappings":"AASA,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,QAAQ,CAAC;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAM,GAAG,MAAM,CAmBxG;AAWD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"launchd.d.ts","sourceRoot":"","sources":["../../src/service/launchd.ts"],"names":[],"mappings":"AASA,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,QAAQ,CAAC;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAM,GAAG,MAAM,CAmBxG;AAWD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,QAAQ,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE,CAAC,GAAG,MAAM,CA0FV"}
|
|
@@ -65,6 +65,26 @@ export function buildLaunchdPlistXml(params) {
|
|
|
65
65
|
const startInterval = interval
|
|
66
66
|
? `\n <key>StartInterval</key>\n <integer>${interval}</integer>\n`
|
|
67
67
|
: '';
|
|
68
|
+
const calendar = params.startCalendarInterval;
|
|
69
|
+
const calHourRaw = Number(calendar?.hour);
|
|
70
|
+
const calMinuteRaw = Number(calendar?.minute);
|
|
71
|
+
const calHour = Number.isFinite(calHourRaw) ? Math.floor(calHourRaw) : NaN;
|
|
72
|
+
const calMinute = Number.isFinite(calMinuteRaw) ? Math.floor(calMinuteRaw) : NaN;
|
|
73
|
+
const hasCalendar = Number.isFinite(calHour)
|
|
74
|
+
&& Number.isFinite(calMinute)
|
|
75
|
+
&& calHour >= 0
|
|
76
|
+
&& calHour <= 23
|
|
77
|
+
&& calMinute >= 0
|
|
78
|
+
&& calMinute <= 59;
|
|
79
|
+
const startCalendarInterval = hasCalendar
|
|
80
|
+
? (`\n <key>StartCalendarInterval</key>\n` +
|
|
81
|
+
` <dict>\n` +
|
|
82
|
+
` <key>Hour</key>\n` +
|
|
83
|
+
` <integer>${calHour}</integer>\n` +
|
|
84
|
+
` <key>Minute</key>\n` +
|
|
85
|
+
` <integer>${calMinute}</integer>\n` +
|
|
86
|
+
` </dict>\n`)
|
|
87
|
+
: '';
|
|
68
88
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
69
89
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
70
90
|
<plist version="1.0">
|
|
@@ -80,7 +100,7 @@ ${programArgsXml}
|
|
|
80
100
|
<key>RunAtLoad</key>
|
|
81
101
|
<true/>
|
|
82
102
|
${keepAlive}
|
|
83
|
-
${startInterval}
|
|
103
|
+
${startCalendarInterval || startInterval}
|
|
84
104
|
${workingDirXml} <key>StandardOutPath</key>
|
|
85
105
|
<string>${xmlEscape(stdoutPath)}</string>
|
|
86
106
|
<key>StandardErrorPath</key>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launchd.js","sourceRoot":"","sources":["../../src/service/launchd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,SAAS,SAAS,CAAC,CAAS;IAC1B,OAAO,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;SACnB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAA6D,EAAE;IAC9F,6EAA6E;IAC7E,gFAAgF;IAChF,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtD,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,MAAM,QAAQ,GAAG,SAAS,CAAC,gEAAgE,CAAC,CAAC;IAC7F,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;QAC1D,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,+BAA+B,CAAC;AAC1D,CAAC;AAED,SAAS,SAAS,CAAC,CAAS;IAC1B,OAAO,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;SACnB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"launchd.js","sourceRoot":"","sources":["../../src/service/launchd.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -119,6 +119,8 @@ test('guided auth login fails closed when Expo web UI is not ready (does not fal
|
|
|
119
119
|
/guid(ed)? login web UI is still not ready|startup failed/i,
|
|
120
120
|
`stderr:\n${res.stderr}`
|
|
121
121
|
);
|
|
122
|
+
assert.match(res.stderr, /Stack runtime path:/i, `stderr:\n${res.stderr}`);
|
|
123
|
+
assert.match(res.stderr, /server health:/i, `stderr:\n${res.stderr}`);
|
|
122
124
|
assert.doesNotMatch(res.stdout, new RegExp(`URL: http://localhost:${fixture.port}\\b`), `stdout:\n${res.stdout}`);
|
|
123
125
|
} finally {
|
|
124
126
|
if (fixture) await fixture.cleanup();
|
|
@@ -2,10 +2,13 @@ import test from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join, resolve } from 'node:path';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
6
7
|
|
|
7
8
|
import { bundleWorkspaceDeps } from './bundleWorkspaceDeps.mjs';
|
|
8
9
|
|
|
10
|
+
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
11
|
+
|
|
9
12
|
function writeJson(path, value) {
|
|
10
13
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
11
14
|
}
|
|
@@ -91,3 +94,25 @@ test('bundleWorkspaceDeps throws when cli-common package.json is malformed', ()
|
|
|
91
94
|
rmSync(repoRoot, { recursive: true, force: true });
|
|
92
95
|
}
|
|
93
96
|
});
|
|
97
|
+
|
|
98
|
+
test('declares external runtime dependencies required by bundled workspace packages', () => {
|
|
99
|
+
const stackPackageJson = JSON.parse(readFileSync(resolve(repoRoot, 'apps', 'stack', 'package.json'), 'utf8'));
|
|
100
|
+
const bundledWorkspacePackagePaths = [
|
|
101
|
+
resolve(repoRoot, 'packages', 'cli-common', 'package.json'),
|
|
102
|
+
resolve(repoRoot, 'packages', 'release-runtime', 'package.json'),
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const stackDependencyNames = new Set(Object.keys(stackPackageJson.dependencies ?? {}));
|
|
106
|
+
const requiredExternalDependencies = new Set();
|
|
107
|
+
for (const packageJsonPath of bundledWorkspacePackagePaths) {
|
|
108
|
+
const bundledPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
109
|
+
for (const dependencyName of Object.keys(bundledPackageJson.dependencies ?? {})) {
|
|
110
|
+
if (!dependencyName.startsWith('@happier-dev/')) {
|
|
111
|
+
requiredExternalDependencies.add(dependencyName);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const missingDependencies = [...requiredExternalDependencies].filter((name) => !stackDependencyNames.has(name));
|
|
117
|
+
assert.deepEqual(missingDependencies, []);
|
|
118
|
+
});
|
package/scripts/mobile.mjs
CHANGED
|
@@ -130,8 +130,8 @@ async function main() {
|
|
|
130
130
|
const stackCtx = resolveStackContext({ env, autostart });
|
|
131
131
|
const { stackMode, runtimeStatePath, stackName, envPath } = stackCtx;
|
|
132
132
|
|
|
133
|
-
// Expo
|
|
134
|
-
//
|
|
133
|
+
// Expo CLI resolves a Metro port early, but it won't actually bind it until late in the native build.
|
|
134
|
+
// If we stick to the default 8081, builds are much more likely to fail late if another Metro/Expo claims 8081 mid-build.
|
|
135
135
|
//
|
|
136
136
|
// Strategy:
|
|
137
137
|
// - If the user explicitly sets --port or HAPPIER_STACK_MOBILE_PORT, honor it.
|
|
@@ -333,11 +333,8 @@ async function main() {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
const configuration = kv.get('--configuration') ?? 'Debug';
|
|
336
|
-
const
|
|
337
|
-
const args = ['run:ios', '--
|
|
338
|
-
if (buildMetroPort) {
|
|
339
|
-
args.push('-p', buildMetroPort);
|
|
340
|
-
}
|
|
336
|
+
const metroPort = String(env.RCT_METRO_PORT ?? portRaw ?? '8081');
|
|
337
|
+
const args = ['run:ios', '--port', metroPort, '--no-build-cache', '--configuration', configuration];
|
|
341
338
|
if (device) {
|
|
342
339
|
args.push('-d', device);
|
|
343
340
|
}
|
|
@@ -3,11 +3,10 @@ import { parseArgs } from './utils/cli/args.mjs';
|
|
|
3
3
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
4
4
|
import { run } from './utils/proc/proc.mjs';
|
|
5
5
|
import { getRootDir } from './utils/paths/paths.mjs';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
6
|
import { banner, cmd, sectionTitle } from './utils/ui/layout.mjs';
|
|
8
7
|
import { cyan, dim, yellow } from './utils/ui/ansi.mjs';
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import { buildMobileDevClientInstallInvocation } from './utils/mobile/dev_client_install_invocation.mjs';
|
|
11
10
|
|
|
12
11
|
async function main() {
|
|
13
12
|
const argv = process.argv.slice(2);
|
|
@@ -18,13 +17,13 @@ async function main() {
|
|
|
18
17
|
printResult({
|
|
19
18
|
json,
|
|
20
19
|
data: {
|
|
21
|
-
flags: ['--device=<id-or-name>', '--clean', '--configuration=Debug|Release', '--json'],
|
|
20
|
+
flags: ['--device=<id-or-name>', '--port=<port>', '--clean', '--configuration=Debug|Release', '--json'],
|
|
22
21
|
},
|
|
23
22
|
text: [
|
|
24
23
|
banner('mobile-dev-client', { subtitle: 'Install the shared iOS dev-client app (one-time).' }),
|
|
25
24
|
'',
|
|
26
25
|
sectionTitle('usage:'),
|
|
27
|
-
` ${cyan('hstack mobile-dev-client')} --install [--device=...] [--clean] [--configuration=Debug|Release] [--json]`,
|
|
26
|
+
` ${cyan('hstack mobile-dev-client')} --install [--device=...] [--port=...] [--clean] [--configuration=Debug|Release] [--json]`,
|
|
28
27
|
'',
|
|
29
28
|
sectionTitle('notes:'),
|
|
30
29
|
`- Installs a dedicated ${cyan('hstack Dev')} Expo dev-client app on your iPhone.`,
|
|
@@ -45,39 +44,15 @@ async function main() {
|
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
const rootDir = getRootDir(import.meta.url);
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const device = kv.get('--device') ?? '';
|
|
51
|
-
const clean = flags.has('--clean');
|
|
52
|
-
const configuration = kv.get('--configuration') ?? 'Debug';
|
|
53
|
-
|
|
54
|
-
const id = defaultDevClientIdentity({ user: process.env.USER ?? process.env.USERNAME ?? 'user' });
|
|
55
|
-
|
|
56
|
-
const args = [
|
|
57
|
-
mobileScript,
|
|
58
|
-
'--app-env=development',
|
|
59
|
-
`--ios-app-name=${id.iosAppName}`,
|
|
60
|
-
`--ios-bundle-id=${id.iosBundleId}`,
|
|
61
|
-
`--scheme=${id.scheme}`,
|
|
62
|
-
'--prebuild',
|
|
63
|
-
...(clean ? ['--clean'] : []),
|
|
64
|
-
'--run-ios',
|
|
65
|
-
`--configuration=${configuration}`,
|
|
66
|
-
'--no-metro',
|
|
67
|
-
...(device ? [`--device=${device}`] : []),
|
|
68
|
-
];
|
|
47
|
+
const invocation = buildMobileDevClientInstallInvocation({ rootDir, argv, baseEnv: process.env });
|
|
69
48
|
|
|
70
49
|
const env = {
|
|
71
|
-
...
|
|
72
|
-
// Ensure Expo app config uses the dev-client scheme.
|
|
73
|
-
EXPO_APP_SCHEME: id.scheme,
|
|
74
|
-
// Ensure per-stack storage isolation is available during dev-client usage.
|
|
75
|
-
EXPO_PUBLIC_HAPPY_STORAGE_SCOPE: process.env.EXPO_PUBLIC_HAPPY_STORAGE_SCOPE ?? '',
|
|
50
|
+
...invocation.env,
|
|
76
51
|
};
|
|
77
52
|
|
|
78
|
-
const out = await run(process.execPath,
|
|
53
|
+
const out = await run(process.execPath, invocation.nodeArgs, { cwd: rootDir, env });
|
|
79
54
|
if (json) {
|
|
80
|
-
printResult({ json, data: { ok: true, installed: true, identity:
|
|
55
|
+
printResult({ json, data: { ok: true, installed: true, identity: invocation.identity, out } });
|
|
81
56
|
}
|
|
82
57
|
}
|
|
83
58
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
7
|
+
|
|
8
|
+
test('mobile-dev-client --help runs without syntax errors', async () => {
|
|
9
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const rootDir = dirname(scriptsDir);
|
|
11
|
+
const script = join(rootDir, 'scripts', 'mobile_dev_client.mjs');
|
|
12
|
+
|
|
13
|
+
const env = {
|
|
14
|
+
...process.env,
|
|
15
|
+
// Prevent env.mjs from selecting a real stack env file (keeps the test fast and hermetic).
|
|
16
|
+
HAPPIER_STACK_ENV_FILE: join(rootDir, 'scripts', 'nonexistent-env'),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const res = await runNodeCapture([script, '--help'], { cwd: rootDir, env });
|
|
20
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`);
|
|
21
|
+
assert.match(res.stdout, /\bmobile-dev-client\b/, `expected help to mention command name\nstdout:\n${res.stdout}`);
|
|
22
|
+
assert.match(res.stdout, /--port(?:=|\b)/, `expected help to mention --port\nstdout:\n${res.stdout}`);
|
|
23
|
+
});
|
|
24
|
+
|
|
@@ -18,7 +18,7 @@ function parseKeyValueLines(text) {
|
|
|
18
18
|
return out;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
test('hstack mobile --run-ios passes
|
|
21
|
+
test('hstack mobile --run-ios passes --port to Expo so the native build and dev server use the same Metro port', async () => {
|
|
22
22
|
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
23
23
|
const mobileScript = join(rootDir, 'scripts', 'mobile.mjs');
|
|
24
24
|
|
|
@@ -72,7 +72,8 @@ process.exit(0);
|
|
|
72
72
|
const env = {
|
|
73
73
|
...process.env,
|
|
74
74
|
// Ensure xcrun runs fast/deterministically in tests.
|
|
75
|
-
|
|
75
|
+
// Include system paths so env.mjs won't prepend /usr/bin ahead of our stub.
|
|
76
|
+
PATH: `${binDir}:/usr/bin:/bin`,
|
|
76
77
|
HAPPIER_STACK_REPO_DIR: repoDir,
|
|
77
78
|
HAPPIER_STACK_HOME_DIR: join(tmp, 'home'),
|
|
78
79
|
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
@@ -90,12 +91,13 @@ process.exit(0);
|
|
|
90
91
|
|
|
91
92
|
assert.ok(port, `expected RCT_METRO_PORT to be set\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
92
93
|
assert.equal(kv.EXPO_PACKAGER_PORT, port, `expected EXPO_PACKAGER_PORT to match\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
93
|
-
assert.ok(args.includes('-p')
|
|
94
|
-
|
|
95
|
-
assert.
|
|
94
|
+
assert.ok(!args.includes('-p'), `expected expo args to not include -p\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
95
|
+
assert.ok(!args.includes('--no-bundler'), `expected expo args to not include --no-bundler\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
96
|
+
assert.ok(args.includes('--port'), `expected expo args to include --port\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
97
|
+
const portIdx = args.indexOf('--port');
|
|
98
|
+
assert.ok(portIdx >= 0 && args[portIdx + 1] === port, `expected --port to match RCT_METRO_PORT\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
96
99
|
assert.notEqual(port, '8081', `expected non-default port to reduce collisions\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
97
100
|
} finally {
|
|
98
101
|
await rm(tmp, { recursive: true, force: true });
|
|
99
102
|
}
|
|
100
103
|
});
|
|
101
|
-
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { chmod, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { getStackRootFromMeta, runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
8
|
+
|
|
9
|
+
function parseKeyValueLines(text) {
|
|
10
|
+
const out = {};
|
|
11
|
+
for (const line of String(text ?? '').split(/\r?\n/)) {
|
|
12
|
+
const idx = line.indexOf('=');
|
|
13
|
+
if (idx === -1) continue;
|
|
14
|
+
const key = line.slice(0, idx).trim();
|
|
15
|
+
const value = line.slice(idx + 1).trim();
|
|
16
|
+
if (key) out[key] = value;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('hstack mobile --run-ios passes --port (long flag) so Expo uses the requested Metro port', async () => {
|
|
22
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
23
|
+
const mobileScript = join(rootDir, 'scripts', 'mobile.mjs');
|
|
24
|
+
|
|
25
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-mobile-runios-longport-'));
|
|
26
|
+
const repoDir = join(tmp, 'repo');
|
|
27
|
+
const storageDir = join(tmp, 'storage');
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const binDir = join(tmp, 'bin');
|
|
31
|
+
await mkdir(binDir, { recursive: true });
|
|
32
|
+
const xcrunStub = join(binDir, 'xcrun');
|
|
33
|
+
await writeFile(
|
|
34
|
+
xcrunStub,
|
|
35
|
+
`#!/bin/bash
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
if [[ "\${1:-}" == "xcdevice" && "\${2:-}" == "list" ]]; then
|
|
38
|
+
echo "[]"
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
echo "xcrun stub: unsupported args: $*" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
`,
|
|
44
|
+
'utf-8'
|
|
45
|
+
);
|
|
46
|
+
if (process.platform !== 'win32') {
|
|
47
|
+
await chmod(xcrunStub, 0o755);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const uiDir = join(repoDir, 'apps', 'ui');
|
|
51
|
+
const expoBin = join(uiDir, 'node_modules', '.bin', 'expo');
|
|
52
|
+
await mkdir(join(uiDir, 'node_modules', '.bin'), { recursive: true });
|
|
53
|
+
|
|
54
|
+
await writeFile(
|
|
55
|
+
expoBin,
|
|
56
|
+
`#!${process.execPath}
|
|
57
|
+
console.log('ARGS=' + JSON.stringify(process.argv.slice(2)));
|
|
58
|
+
console.log('RCT_METRO_PORT=' + (process.env.RCT_METRO_PORT ?? ''));
|
|
59
|
+
console.log('EXPO_PACKAGER_PORT=' + (process.env.EXPO_PACKAGER_PORT ?? ''));
|
|
60
|
+
process.exit(0);
|
|
61
|
+
`,
|
|
62
|
+
'utf-8'
|
|
63
|
+
);
|
|
64
|
+
if (process.platform !== 'win32') {
|
|
65
|
+
await chmod(expoBin, 0o755);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await mkdir(join(storageDir, 'main'), { recursive: true });
|
|
69
|
+
|
|
70
|
+
const env = {
|
|
71
|
+
...process.env,
|
|
72
|
+
// Ensure our xcrun stub wins over /usr/bin/xcrun even after env.mjs normalizes PATH.
|
|
73
|
+
PATH: `${binDir}:/usr/bin:/bin`,
|
|
74
|
+
HAPPIER_STACK_REPO_DIR: repoDir,
|
|
75
|
+
HAPPIER_STACK_HOME_DIR: join(tmp, 'home'),
|
|
76
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
77
|
+
HAPPIER_STACK_STACK: 'main',
|
|
78
|
+
HAPPIER_STACK_TAILSCALE_PREFER_PUBLIC_URL: '0',
|
|
79
|
+
HAPPIER_STACK_TAILSCALE_SERVE: '0',
|
|
80
|
+
HAPPIER_STACK_ENV_FILE: join(tmp, 'nonexistent-env'),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const res = await runNodeCapture([mobileScript, '--run-ios', '--no-metro', '--port=14362'], { cwd: rootDir, env });
|
|
84
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`);
|
|
85
|
+
const kv = parseKeyValueLines(res.stdout);
|
|
86
|
+
const args = JSON.parse(kv.ARGS ?? '[]');
|
|
87
|
+
|
|
88
|
+
assert.ok(!args.includes('--no-bundler'), `expected expo args to not include --no-bundler\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
89
|
+
assert.ok(args.includes('--port'), `expected expo args to include --port\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
90
|
+
assert.ok(!args.includes('-p'), `expected expo args to not include -p\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
91
|
+
const portIdx = args.indexOf('--port');
|
|
92
|
+
assert.ok(portIdx >= 0 && args[portIdx + 1] === '14362', `expected --port 14362\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
93
|
+
assert.equal(
|
|
94
|
+
kv.RCT_METRO_PORT,
|
|
95
|
+
'14362',
|
|
96
|
+
`expected RCT_METRO_PORT to be set from hstack --port\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
97
|
+
);
|
|
98
|
+
assert.equal(
|
|
99
|
+
kv.EXPO_PACKAGER_PORT,
|
|
100
|
+
'14362',
|
|
101
|
+
`expected EXPO_PACKAGER_PORT to match\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
102
|
+
);
|
|
103
|
+
} finally {
|
|
104
|
+
await rm(tmp, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
});
|
package/scripts/remote_cmd.mjs
CHANGED
|
@@ -84,10 +84,16 @@ function usageText() {
|
|
|
84
84
|
' [--server-url=<url>] [--webapp-url=<url>] [--public-server-url=<url>]',
|
|
85
85
|
' [--json]',
|
|
86
86
|
'',
|
|
87
|
+
' hstack remote server setup --ssh <user@host> [--preview|--stable] [--channel <stable|preview>]',
|
|
88
|
+
' [--mode <user|system>]',
|
|
89
|
+
' [--env KEY=VALUE]...',
|
|
90
|
+
' [--json]',
|
|
91
|
+
'',
|
|
87
92
|
'notes:',
|
|
88
93
|
' - This command runs remote operations over ssh.',
|
|
89
94
|
' - It installs the Happier CLI on the remote host, pairs credentials, and optionally installs/starts the daemon service.',
|
|
90
95
|
' - Default service mode is user; set --service none to skip daemon service setup.',
|
|
96
|
+
' - Remote server setup installs the self-host runtime as a service (default: user mode).',
|
|
91
97
|
].join('\n');
|
|
92
98
|
}
|
|
93
99
|
|
|
@@ -117,6 +123,43 @@ function resolveService(argv) {
|
|
|
117
123
|
return v || 'user';
|
|
118
124
|
}
|
|
119
125
|
|
|
126
|
+
function resolveMode(argv) {
|
|
127
|
+
if (argv.includes('--system')) return 'system';
|
|
128
|
+
if (argv.includes('--user')) return 'user';
|
|
129
|
+
const picked = argv.find((a) => a === '--mode' || a.startsWith('--mode='));
|
|
130
|
+
if (!picked) return 'user';
|
|
131
|
+
if (picked === '--mode') {
|
|
132
|
+
const idx = argv.indexOf('--mode');
|
|
133
|
+
const v = String(argv[idx + 1] ?? '').trim().toLowerCase();
|
|
134
|
+
return v || 'user';
|
|
135
|
+
}
|
|
136
|
+
const v = String(picked.slice('--mode='.length)).trim().toLowerCase();
|
|
137
|
+
return v || 'user';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function collectEnvValues(argv) {
|
|
141
|
+
const args = Array.isArray(argv) ? argv.map(String) : [];
|
|
142
|
+
const values = [];
|
|
143
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
144
|
+
const a = args[i] ?? '';
|
|
145
|
+
if (a === '--env') {
|
|
146
|
+
const next = args[i + 1] ?? '';
|
|
147
|
+
if (!next || next.startsWith('--')) {
|
|
148
|
+
throw new Error('[remote] missing value for --env (expected KEY=VALUE)');
|
|
149
|
+
}
|
|
150
|
+
values.push(String(next));
|
|
151
|
+
i += 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (a.startsWith('--env=')) {
|
|
155
|
+
const raw = a.slice('--env='.length);
|
|
156
|
+
if (!raw) throw new Error('[remote] missing value for --env (expected KEY=VALUE)');
|
|
157
|
+
values.push(String(raw));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return values;
|
|
161
|
+
}
|
|
162
|
+
|
|
120
163
|
async function runRemoteDaemonSetup(argvRaw) {
|
|
121
164
|
const argv0 = argvRaw.slice();
|
|
122
165
|
const json = wantsJson(argv0);
|
|
@@ -209,6 +252,71 @@ async function runRemoteDaemonSetup(argvRaw) {
|
|
|
209
252
|
});
|
|
210
253
|
}
|
|
211
254
|
|
|
255
|
+
async function runRemoteServerSetup(argvRaw) {
|
|
256
|
+
const argv0 = argvRaw.slice();
|
|
257
|
+
const json = wantsJson(argv0);
|
|
258
|
+
|
|
259
|
+
let args = argv0.slice();
|
|
260
|
+
const ssh = takeFlagValue(args, '--ssh');
|
|
261
|
+
args = ssh.rest;
|
|
262
|
+
if (!ssh.value) {
|
|
263
|
+
process.stderr.write('Missing required flag: --ssh <user@host>\n');
|
|
264
|
+
process.exit(2);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const channel = resolveChannel(argv0);
|
|
268
|
+
if (channel !== 'stable' && channel !== 'preview') {
|
|
269
|
+
throw new Error(`[remote] invalid --channel value: ${channel}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const mode = resolveMode(argv0);
|
|
273
|
+
if (mode !== 'user' && mode !== 'system') {
|
|
274
|
+
throw new Error(`[remote] invalid --mode value: ${mode} (expected user or system)`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const envValues = collectEnvValues(argv0);
|
|
278
|
+
|
|
279
|
+
const installUrl = 'https://happier.dev/install';
|
|
280
|
+
const remoteHstack = '$HOME/.happier/bin/hstack';
|
|
281
|
+
|
|
282
|
+
// Always disable auto-service setup in the installer so this command controls remote service behavior.
|
|
283
|
+
const installCmd = [
|
|
284
|
+
`curl -fsSL ${installUrl} |`,
|
|
285
|
+
`HAPPIER_CHANNEL=${channel} HAPPIER_WITH_DAEMON=0 HAPPIER_NONINTERACTIVE=1 bash`,
|
|
286
|
+
].join(' ');
|
|
287
|
+
|
|
288
|
+
await runSsh({ target: ssh.value, command: installCmd });
|
|
289
|
+
|
|
290
|
+
const envArgs = envValues.map((value) => `--env ${safeBashSingleQuote(value)}`).join(' ');
|
|
291
|
+
const baseSelfHostCmd = [
|
|
292
|
+
remoteHstack,
|
|
293
|
+
'self-host',
|
|
294
|
+
'install',
|
|
295
|
+
`--channel=${channel}`,
|
|
296
|
+
`--mode=${mode}`,
|
|
297
|
+
'--without-cli',
|
|
298
|
+
'--non-interactive',
|
|
299
|
+
'--json',
|
|
300
|
+
].join(' ');
|
|
301
|
+
const selfHostCmd = `${mode === 'system' ? 'sudo -E ' : ''}${baseSelfHostCmd}${envArgs ? ` ${envArgs}` : ''}`;
|
|
302
|
+
|
|
303
|
+
await runSsh({ target: ssh.value, command: selfHostCmd });
|
|
304
|
+
|
|
305
|
+
printResult({
|
|
306
|
+
json,
|
|
307
|
+
data: { ok: true, ssh: ssh.value, channel, mode, env: envValues },
|
|
308
|
+
text: json
|
|
309
|
+
? null
|
|
310
|
+
: [
|
|
311
|
+
'✓ Remote server setup complete',
|
|
312
|
+
`- ssh: ${ssh.value}`,
|
|
313
|
+
`- channel: ${channel}`,
|
|
314
|
+
`- mode: ${mode}`,
|
|
315
|
+
`- env: ${envValues.length ? envValues.join(', ') : '(none)'}`,
|
|
316
|
+
].join('\n'),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
212
320
|
async function main() {
|
|
213
321
|
const argvRaw = process.argv.slice(2);
|
|
214
322
|
if (argvRaw.length === 0 || wantsHelp(argvRaw)) {
|
|
@@ -224,6 +332,10 @@ async function main() {
|
|
|
224
332
|
await runRemoteDaemonSetup(argvRaw);
|
|
225
333
|
return;
|
|
226
334
|
}
|
|
335
|
+
if (top === 'server' && sub === 'setup') {
|
|
336
|
+
await runRemoteServerSetup(argvRaw);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
227
339
|
|
|
228
340
|
printResult({
|
|
229
341
|
json: wantsJson(argvRaw),
|