@myerscarpenter/quest-dev 1.2.1 → 1.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 +65 -1
- package/build/commands/battery.js +3 -3
- package/build/commands/battery.js.map +1 -1
- package/build/commands/stay-awake.d.ts +31 -4
- package/build/commands/stay-awake.d.ts.map +1 -1
- package/build/commands/stay-awake.js +160 -83
- package/build/commands/stay-awake.js.map +1 -1
- package/build/index.js +94 -19
- package/build/index.js.map +1 -1
- package/build/utils/adb.d.ts +10 -3
- package/build/utils/adb.d.ts.map +1 -1
- package/build/utils/adb.js +10 -13
- package/build/utils/adb.js.map +1 -1
- package/build/utils/config.d.ts +24 -0
- package/build/utils/config.d.ts.map +1 -0
- package/build/utils/config.js +82 -0
- package/build/utils/config.js.map +1 -0
- package/docs/tests-with-hardware.md +128 -0
- package/package.json +3 -2
- package/src/commands/battery.ts +3 -3
- package/src/commands/stay-awake.ts +184 -96
- package/src/index.ts +104 -22
- package/src/utils/adb.ts +17 -13
- package/src/utils/config.ts +88 -0
- package/tests/config.test.ts +88 -0
- package/tests/stay-awake.test.ts +71 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config file loading for quest-dev
|
|
3
|
+
* Resolves settings from CLI flags → .quest-dev.json → ~/.config/quest-dev/config.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
|
|
10
|
+
export interface QuestDevConfig {
|
|
11
|
+
pin?: string;
|
|
12
|
+
idleTimeout?: number;
|
|
13
|
+
lowBattery?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const CONFIG_LOCATIONS = [
|
|
17
|
+
join(process.cwd(), '.quest-dev.json'),
|
|
18
|
+
join(homedir(), '.config', 'quest-dev', 'config.json'),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function tryReadConfig(path: string): QuestDevConfig | null {
|
|
22
|
+
try {
|
|
23
|
+
const content = readFileSync(path, 'utf-8');
|
|
24
|
+
return JSON.parse(content);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load merged config from all config file locations.
|
|
32
|
+
* First file found wins for each field.
|
|
33
|
+
*/
|
|
34
|
+
export function loadConfig(): QuestDevConfig {
|
|
35
|
+
const merged: QuestDevConfig = {};
|
|
36
|
+
|
|
37
|
+
for (const path of CONFIG_LOCATIONS) {
|
|
38
|
+
const config = tryReadConfig(path);
|
|
39
|
+
if (!config) continue;
|
|
40
|
+
if (merged.pin === undefined && config.pin) merged.pin = config.pin;
|
|
41
|
+
if (merged.idleTimeout === undefined && config.idleTimeout !== undefined) merged.idleTimeout = config.idleTimeout;
|
|
42
|
+
if (merged.lowBattery === undefined && config.lowBattery !== undefined) merged.lowBattery = config.lowBattery;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return merged;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Save config values to ~/.config/quest-dev/config.json
|
|
50
|
+
* Merges with existing config (doesn't overwrite unrelated fields).
|
|
51
|
+
*/
|
|
52
|
+
export function saveConfig(values: QuestDevConfig): string {
|
|
53
|
+
const configPath = join(homedir(), '.config', 'quest-dev', 'config.json');
|
|
54
|
+
let existing: QuestDevConfig = {};
|
|
55
|
+
try {
|
|
56
|
+
existing = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
57
|
+
} catch {
|
|
58
|
+
// No existing config, start fresh
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const merged = { ...existing };
|
|
62
|
+
if (values.pin !== undefined) merged.pin = values.pin;
|
|
63
|
+
if (values.idleTimeout !== undefined) merged.idleTimeout = values.idleTimeout;
|
|
64
|
+
if (values.lowBattery !== undefined) merged.lowBattery = values.lowBattery;
|
|
65
|
+
|
|
66
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
67
|
+
writeFileSync(configPath, JSON.stringify(merged, null, 2) + '\n');
|
|
68
|
+
return configPath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve PIN from CLI flag, then config files
|
|
73
|
+
*/
|
|
74
|
+
export function loadPin(cliPin?: string): string {
|
|
75
|
+
if (cliPin) return cliPin;
|
|
76
|
+
|
|
77
|
+
const config = loadConfig();
|
|
78
|
+
if (config.pin) return config.pin;
|
|
79
|
+
|
|
80
|
+
console.error('Error: No PIN found');
|
|
81
|
+
console.error('');
|
|
82
|
+
console.error('Provide a PIN via one of:');
|
|
83
|
+
console.error(' --pin <pin> CLI flag');
|
|
84
|
+
console.error(' quest-dev config --pin <pin> Save as default');
|
|
85
|
+
console.error('');
|
|
86
|
+
console.error('The PIN is your Meta Store PIN for the logged-in account.');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { loadConfig, loadPin } from '../src/utils/config.js';
|
|
4
|
+
|
|
5
|
+
vi.mock('fs');
|
|
6
|
+
|
|
7
|
+
const mockReadFileSync = vi.mocked(fs.readFileSync);
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.resetAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('loadConfig', () => {
|
|
14
|
+
it('returns empty object when no config files exist', () => {
|
|
15
|
+
mockReadFileSync.mockImplementation(() => {
|
|
16
|
+
throw new Error('ENOENT');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
expect(config).toEqual({});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('reads .quest-dev.json from cwd when present', () => {
|
|
24
|
+
mockReadFileSync.mockImplementation((path) => {
|
|
25
|
+
if (String(path).endsWith('.quest-dev.json')) {
|
|
26
|
+
return JSON.stringify({ pin: '1234' });
|
|
27
|
+
}
|
|
28
|
+
throw new Error('ENOENT');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const config = loadConfig();
|
|
32
|
+
expect(config.pin).toBe('1234');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('merges configs: local file wins over global file', () => {
|
|
36
|
+
mockReadFileSync.mockImplementation((path) => {
|
|
37
|
+
if (String(path).endsWith('.quest-dev.json')) {
|
|
38
|
+
return JSON.stringify({ pin: 'local-pin', idleTimeout: 5000 });
|
|
39
|
+
}
|
|
40
|
+
if (String(path).endsWith('config.json')) {
|
|
41
|
+
return JSON.stringify({ pin: 'global-pin', lowBattery: 15 });
|
|
42
|
+
}
|
|
43
|
+
throw new Error('ENOENT');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const config = loadConfig();
|
|
47
|
+
expect(config.pin).toBe('local-pin');
|
|
48
|
+
expect(config.idleTimeout).toBe(5000);
|
|
49
|
+
expect(config.lowBattery).toBe(15);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('loadPin', () => {
|
|
54
|
+
it('returns CLI pin when provided', () => {
|
|
55
|
+
// Should not even read config files
|
|
56
|
+
const pin = loadPin('cli-pin');
|
|
57
|
+
expect(pin).toBe('cli-pin');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('falls back to config file pin', () => {
|
|
61
|
+
mockReadFileSync.mockImplementation((path) => {
|
|
62
|
+
if (String(path).endsWith('.quest-dev.json')) {
|
|
63
|
+
return JSON.stringify({ pin: 'config-pin' });
|
|
64
|
+
}
|
|
65
|
+
throw new Error('ENOENT');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const pin = loadPin();
|
|
69
|
+
expect(pin).toBe('config-pin');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('exits with error when no pin found', () => {
|
|
73
|
+
mockReadFileSync.mockImplementation(() => {
|
|
74
|
+
throw new Error('ENOENT');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
78
|
+
throw new Error('process.exit');
|
|
79
|
+
});
|
|
80
|
+
const mockError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
81
|
+
|
|
82
|
+
expect(() => loadPin()).toThrow('process.exit');
|
|
83
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
84
|
+
|
|
85
|
+
mockExit.mockRestore();
|
|
86
|
+
mockError.mockRestore();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseTestProperties, buildSetPropertyArgs } from '../src/commands/stay-awake.js';
|
|
3
|
+
|
|
4
|
+
describe('parseTestProperties', () => {
|
|
5
|
+
it('parses a full Bundle output', () => {
|
|
6
|
+
const output = 'Bundle[{disable_guardian=true, set_proximity_close=true, disable_dialogs=true, disable_autosleep=true}]';
|
|
7
|
+
const props = parseTestProperties(output);
|
|
8
|
+
expect(props).toEqual({
|
|
9
|
+
disable_guardian: true,
|
|
10
|
+
set_proximity_close: true,
|
|
11
|
+
disable_dialogs: true,
|
|
12
|
+
disable_autosleep: true,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('parses Bundle with false values', () => {
|
|
17
|
+
const output = 'Bundle[{disable_guardian=false, set_proximity_close=false, disable_dialogs=false, disable_autosleep=false}]';
|
|
18
|
+
const props = parseTestProperties(output);
|
|
19
|
+
expect(props).toEqual({
|
|
20
|
+
disable_guardian: false,
|
|
21
|
+
set_proximity_close: false,
|
|
22
|
+
disable_dialogs: false,
|
|
23
|
+
disable_autosleep: false,
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns defaults for unparseable output', () => {
|
|
28
|
+
const props = parseTestProperties('some garbage output');
|
|
29
|
+
expect(props).toEqual({
|
|
30
|
+
disable_guardian: false,
|
|
31
|
+
set_proximity_close: false,
|
|
32
|
+
disable_dialogs: false,
|
|
33
|
+
disable_autosleep: false,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns defaults for empty string', () => {
|
|
38
|
+
const props = parseTestProperties('');
|
|
39
|
+
expect(props).toEqual({
|
|
40
|
+
disable_guardian: false,
|
|
41
|
+
set_proximity_close: false,
|
|
42
|
+
disable_dialogs: false,
|
|
43
|
+
disable_autosleep: false,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('buildSetPropertyArgs', () => {
|
|
49
|
+
it('builds enable args with correct PIN', () => {
|
|
50
|
+
const args = buildSetPropertyArgs('5678', true);
|
|
51
|
+
expect(args).toEqual([
|
|
52
|
+
'shell', 'content', 'call',
|
|
53
|
+
'--uri', 'content://com.oculus.rc',
|
|
54
|
+
'--method', 'SET_PROPERTY',
|
|
55
|
+
'--extra', 'disable_guardian:b:true',
|
|
56
|
+
'--extra', 'disable_dialogs:b:true',
|
|
57
|
+
'--extra', 'disable_autosleep:b:true',
|
|
58
|
+
'--extra', 'set_proximity_close:b:true',
|
|
59
|
+
'--extra', 'PIN:s:5678',
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('builds disable args', () => {
|
|
64
|
+
const args = buildSetPropertyArgs('1234', false);
|
|
65
|
+
expect(args).toContain('disable_guardian:b:false');
|
|
66
|
+
expect(args).toContain('disable_dialogs:b:false');
|
|
67
|
+
expect(args).toContain('disable_autosleep:b:false');
|
|
68
|
+
expect(args).toContain('set_proximity_close:b:false');
|
|
69
|
+
expect(args).toContain('PIN:s:1234');
|
|
70
|
+
});
|
|
71
|
+
});
|