neutral names in docs, tests and comments (no real app or project names)

This commit is contained in:
CRTkafa 2026-10-16 06:14:02 +08:00
parent cba4660777
commit 6443a5c442
7 changed files with 28 additions and 28 deletions

View file

@ -55,12 +55,12 @@ fn updater_has_a_real_minisign_key_and_only_storebound_dev() {
#[test]
fn open_deep_link_takes_one_plain_absolute_path() {
let p = |s: &str| deep_link_open_path(&Url::parse(s).unwrap());
let abs = if cfg!(windows) { "C:%5Cgames%5CDemo App" } else { "%2FUsers%2Fyou%2Fgames%2FDemo App" };
let want = if cfg!(windows) { "C:\\games\\Demo App" } else { "/Users/you/games/Demo App" };
let abs = if cfg!(windows) { "C:%5Cgames%5Cdemo-app" } else { "%2FUsers%2Fyou%2Fgames%2Fdemo-app" };
let want = if cfg!(windows) { "C:\\games\\demo-app" } else { "/Users/you/games/demo-app" };
assert_eq!(p(&format!("storebound://open?path={abs}")), Some(PathBuf::from(want)));
assert_eq!(p(&format!("storebound://open/?path={abs}")), Some(PathBuf::from(want)));
for bad in [
"storebound://open?path=games%2FDemo App".to_string(),
"storebound://open?path=games%2Fdemo-app".to_string(),
"storebound://open?path=".to_string(),
"storebound://open".to_string(),
format!("storebound://open?path={abs}&path={abs}"),

View file

@ -46,7 +46,7 @@ fn ids_match_the_cli() {
// cli/lib/workspace.ts accountId and cli/lib/registry.ts folderId give the same values
assert_eq!(profiles::account_id(AccountKind::Apple, "acme"), "747351dbf2569d17");
assert_eq!(profiles::account_id(AccountKind::Play, "default"), "82a8c2561d950999");
assert_eq!(registry::folder_id("/w/Star Dash"), "f4994b9746e7eb18");
assert_eq!(registry::folder_id("/w/star-dash"), "c27311c5023dc3ba");
assert!(profiles::valid_name("default") && profiles::valid_name("acme-games.2") && !profiles::valid_name("../x") && !profiles::valid_name("-a"));
assert_eq!(profiles::slug("Acme Games: Play!"), "acme-games-play");
assert_eq!(profiles::slug("???"), "account");
@ -133,19 +133,19 @@ fn project(id: &str, path: &str, play: Option<&str>) -> Project {
fn the_folder_list_is_the_machines() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("projects.json");
let mut st = State { projects: vec![project("aaaaaaaaaaaaaaaa", "/w/Star Dash", Some("acc"))], ..State::default() };
let mut st = State { projects: vec![project("aaaaaaaaaaaaaaaa", "/w/star-dash", Some("acc"))], ..State::default() };
// first start: the app's list becomes the shared one
assert!(!registry::sync(&mut st, &file).unwrap());
assert_eq!(registry::read(&file).unwrap()[0].id, "aaaaaaaaaaaaaaaa");
// the CLI adds a folder (storebound desktop / apps add): the app shows it, the first keeps its accounts
let mut list = registry::read(&file).unwrap();
list.push(Entry { id: registry::folder_id("/w/a game"), path: "/w/a game".into(), name: "a game".into(), added: "t".into() });
list.push(Entry { id: registry::folder_id("/w/demo-game"), path: "/w/demo-game".into(), name: "demo-game".into(), added: "t".into() });
registry::write(&file, &list).unwrap();
assert!(registry::sync(&mut st, &file).unwrap());
assert_eq!(st.projects.iter().map(|p| p.path.as_str()).collect::<Vec<_>>(), vec!["/w/Star Dash", "/w/a game"]);
assert_eq!(st.projects.iter().map(|p| p.path.as_str()).collect::<Vec<_>>(), vec!["/w/star-dash", "/w/demo-game"]);
assert_eq!(st.projects[0].play_account.as_deref(), Some("acc"));
// the CLI removes one (apps remove): it leaves the app with its overrides
st.overrides.ignored.push(format!("project:{}", registry::folder_id("/w/a game")));
st.overrides.ignored.push(format!("project:{}", registry::folder_id("/w/demo-game")));
registry::write(&file, &list[..1]).unwrap();
assert!(registry::sync(&mut st, &file).unwrap());
assert_eq!(st.projects.len(), 1);
@ -155,7 +155,7 @@ fn the_folder_list_is_the_machines() {
registry::write(&file, &[list[0].clone(), Entry { id: registry::folder_id("/w/cli"), path: "/w/cli".into(), name: "cli".into(), added: String::new() }]).unwrap();
registry::publish(&st, &file, &[]).unwrap();
let paths: Vec<String> = registry::read(&file).unwrap().into_iter().map(|e| e.path).collect();
assert_eq!(paths, vec!["/w/Star Dash", "/w/third", "/w/cli"]);
assert_eq!(paths, vec!["/w/star-dash", "/w/third", "/w/cli"]);
// bad entries are dropped, not the whole file
assert_eq!(registry::parse(r#"{"projects":[{"path":"rel"},{"path":"/a","id":"XYZ"},{"path":"/a"}]}"#).iter().map(|e| e.id.clone()).collect::<Vec<_>>(), vec![registry::folder_id("/a")]);
}
@ -173,7 +173,7 @@ fn store_only_apps_join_their_profiles_account_and_survive_folder_writes() {
assert_eq!(apps.iter().map(|a| a.app_id.as_str()).collect::<Vec<_>>(), vec!["6400000999", "com.example.legacy"]);
assert_eq!(apps[1].bundle_id.as_deref(), Some("com.example.legacy"));
// the app writes its folder list: the store-only entries stay
let mut st = State { projects: vec![project("aaaaaaaaaaaaaaaa", "/w/Star Dash", None)], ..State::default() };
let mut st = State { projects: vec![project("aaaaaaaaaaaaaaaa", "/w/star-dash", None)], ..State::default() };
registry::publish(&st, &file, &[]).unwrap();
assert_eq!(registry::read_store_apps(&file).len(), 2);
assert!(registry::sync(&mut st, &file).is_ok());

View file

@ -316,7 +316,7 @@ candidate on a tag (Jenkins still publishes site/, and `publish` storebound.dev/
3. **Windows runner on the VDS (done).** Windows Server 2022 Standard (evaluation) in a KVM virtual machine, in
Docker (`dockurr/windows:6.05`), `/srv/windows-runner/`. Forgejo runner 13.2.0 (`vds-windows`, label
`windows:host`, capacity 1, job timeout 3 h), registered at the **CRT user level**: every CRT repository can use
it (storebound, Demo App, ...).
it (storebound, demo-app, ...).
- **Resources:** 8 vCPUs, 12 GB RAM, a 150 GB disk (`storage/data.img`, sparse). `restart: unless-stopped`.
- **Network:** nothing public. The web viewer (8006), RDP (3389) and the VM's OpenSSH (22, as host port 2223) are
bound to 127.0.0.1 on the VDS; use an SSH tunnel (`ssh -L 8006:127.0.0.1:8006 root@<vds>`, then

View file

@ -126,9 +126,9 @@ describe('the installed app', () => {
expect(findInstalled('linux', {}, at([]), '/home/you', () => ['storeboundish.AppImage'])).toBeNull();
});
test('opened with the folder as a deep link, never through a shell', () => {
const link = openFolderLink('/Users/you/games/Demo App & co');
expect(link).toBe('storebound://open?path=%2FUsers%2Fyou%2Fgames%2FDemo App%20%26%20co');
expect(new URL(link).searchParams.get('path')).toBe('/Users/you/games/Demo App & co');
const link = openFolderLink('/Users/you/games/demo-app & co');
expect(link).toBe('storebound://open?path=%2FUsers%2Fyou%2Fgames%2Fdemo-app%20%26%20co');
expect(new URL(link).searchParams.get('path')).toBe('/Users/you/games/demo-app & co');
expect(openCommand('darwin', { path: '/Applications/storebound.app', how: 'bundle' }, link)).toEqual(['open', '-b', 'dev.storebound.desktop', link]);
expect(openCommand('darwin', { path: '/Applications/storebound.app', how: 'bundle' }, null)).toEqual(['open', '-b', 'dev.storebound.desktop']);
expect(openCommand('win32', { path: 'C:\\x\\storebound-desktop.exe', how: 'exe' }, link)).toEqual(['C:\\x\\storebound-desktop.exe', link]);

View file

@ -66,7 +66,7 @@ describe('credentials: one machine, many projects', () => {
writeFileSync(join(creds, 'default.json'), JSON.stringify({ ASC_KEY_ID: 'DEFAULT000', ASC_ISSUER_ID: 'iss', ASC_P8: PEM('x') }));
expect(defaultProfile()).toBe('teamB');
// the project's own keys beat the pointer; a profile the project names beats both; env beats all
const a = project(base, 'Star Dash');
const a = project(base, 'star-dash');
expect(ascKey(a).keyId).toBe('ABCDE12345');
const file = join(a, '.storebound', 'credentials.json');
writeFileSync(file, JSON.stringify({ ...JSON.parse(readFileSync(file, 'utf8')), account: 'teamA' }));
@ -84,7 +84,7 @@ describe('credentials: one machine, many projects', () => {
}));
test('project-only values from the project, store keys from the profile, env over everything', () => machine(({ creds, base }) => {
const a = project(base, 'Star Dash');
const a = project(base, 'star-dash');
// the project's own keys work without any profile
expect(ascKey(a).keyId).toBe('ABCDE12345');
mkdirSync(creds, { recursive: true });
@ -113,7 +113,7 @@ describe('credentials: one machine, many projects', () => {
}));
test('accounts import: two projects with the same Apple key and Play account -> one profile, 0600 files, projects stay valid', () => machine(({ creds, base }) => {
const a = project(base, 'Star Dash'), b = project(base, 'a game');
const a = project(base, 'star-dash'), b = project(base, 'demo-game');
const dry = planImport(a);
expect(dry.profile).toBe('default');
expect(dry.reuse).toBe(false);
@ -136,7 +136,7 @@ describe('credentials: one machine, many projects', () => {
const projA = JSON.parse(readFileSync(join(a, '.storebound', 'credentials.json'), 'utf8'));
expect(projA.account).toBe('default');
expect(projA.ASC_KEY_ID).toBeUndefined();
expect(projA.ANDROID_KEYSTORE).toBe('Star Dash.jks');
expect(projA.ANDROID_KEYSTORE).toBe('star-dash.jks');
expect(ascKey(a).pem).toContain('apple-secret-sentinel');
expect(playServiceAccount(a).client_email).toBe('release@example.com');
// the second project with the same account: the same profile, its files unchanged
@ -145,7 +145,7 @@ describe('credentials: one machine, many projects', () => {
expect(second.reuse).toBe(true);
expect(second.writes.filter(w => w.path.startsWith(creds)).map(w => w.action)).toEqual(['same']);
applyImport(second);
expect(JSON.parse(readFileSync(join(b, '.storebound', 'credentials.json'), 'utf8')).ANDROID_KEYSTORE_PASSWORD).toBe('a game-pw');
expect(JSON.parse(readFileSync(join(b, '.storebound', 'credentials.json'), 'utf8')).ANDROID_KEYSTORE_PASSWORD).toBe('demo-game-pw');
expect(ascKey(b).keyId).toBe('ABCDE12345');
// running it again changes nothing
expect(planImport(b).writes.every(w => w.action === 'same')).toBe(true);
@ -157,13 +157,13 @@ describe('credentials: one machine, many projects', () => {
expect(third.profile).toBe('other-studio');
applyImport(third);
expect(ascKey(c).keyId).toBe('ZZZZZ99999');
expect(ascKey(a).keyId).toBe('ABCDE12345'); // "account": "default" still points Star Dash at its own
expect(ascKey(a).keyId).toBe('ABCDE12345'); // "account": "default" still points star-dash at its own
expect(useProfile(c, 'default')).toContain('credentials.json');
expect(() => useProfile(c, 'missing')).toThrow(/no profile/);
}));
test('the import command prints paths and names, never key values', () => machine(({ base }) => {
const a = project(base, 'Star Dash');
const a = project(base, 'star-dash');
const r = Bun.spawnSync(['bun', join(ROOT, 'cli', 'index.ts'), 'accounts', 'import', '--dry', '--dir', a], { env: { ...process.env, STOREBOUND_NO_NETWORK: '1' } });
const out = r.stdout.toString() + r.stderr.toString();
expect(r.exitCode).toBe(0);
@ -179,14 +179,14 @@ describe('credentials: one machine, many projects', () => {
describe('the machine\'s app folder list', () => {
test('add, re-add, remove; ids are the desktop export ids; bad entries are dropped', () => machine(({ base }) => {
const r0 = { version: 1 as const, projects: [] };
const { registry, added } = withFolder(r0, '/w/Star Dash', 'Star Dash', 't');
const { registry, added } = withFolder(r0, '/w/star-dash', 'Star Dash', 't');
expect(added).toBe(true);
expect(registry.projects[0]!.id).toBe(desktopProjectId('/w/Star Dash'));
expect(withFolder(registry, '/w/Star Dash/', undefined, 't').added).toBe(false);
expect(withoutFolder(registry, folderId('/w/Star Dash')).removed?.name).toBe('Star Dash');
expect(registry.projects[0]!.id).toBe(desktopProjectId('/w/star-dash'));
expect(withFolder(registry, '/w/star-dash/', undefined, 't').added).toBe(false);
expect(withoutFolder(registry, folderId('/w/star-dash')).removed?.name).toBe('Star Dash');
expect(parseRegistry('{"projects":[{"path":"relative"},{"path":"/a","id":"zz"},{"path":"/a"}]}').projects.map(p => [p.path, p.id])).toEqual([['/a', folderId('/a')]]);
expect(parseRegistry('not json').projects).toEqual([]);
const dir = project(base, 'a game');
const dir = project(base, 'demo-game');
expect(addFolder(dir).added).toBe(true);
expect(addFolder(dir).added).toBe(false);
expect(readRegistry().projects.map(p => p.path)).toEqual([dir]);

View file

@ -317,7 +317,7 @@ describe('store-only apps in the machine list', () => {
expect(reg.storeApps).toEqual([{ id: storeAppId('apple', '6400000999'), store: 'apple', appId: '6400000999', account: 'teamA', name: 'Demo App', bundleId: null, added: reg.storeApps![0]!.added }]);
expect(reg.projects).toEqual([]);
// a folder added later keeps the store-only entry
const f = withFolder(reg, '/w/Star Dash', undefined, 't').registry;
const f = withFolder(reg, '/w/star-dash', undefined, 't').registry;
expect(f.storeApps).toHaveLength(1);
expect(parseRegistry(JSON.stringify(f)).storeApps).toHaveLength(1);
const again = await capture(() => apps(parseArgs(['add', '--store', 'apple', '--app-id', '6400000999', '--account', 'teamA', '--name', 'Demo App'], [])));

View file

@ -51,7 +51,7 @@ describe('template answers', () => {
});
test('a config from before `unanswered` is recognised by the template values; [] means answered', () => {
const legacy = { app: { name: 'Demo Blast', copyright: '2026 Example Studio' }, apple: { bundleId: 'x', info: { reviewNotes: 'mine', ageRating: { advertising: true } } },
const legacy = { app: { name: 'DEMO BLAST', copyright: '2026 Example Studio' }, apple: { bundleId: 'x', info: { reviewNotes: 'mine', ageRating: { advertising: true } } },
dataSafety: { collectsData: true, encryptedInTransit: true, accountCreation: ['PSL_ACM_NONE'], data: {
PSL_APPROX_LOCATION: { shared: true, collect: ['analytics', 'advertising', 'fraud'], share: ['analytics', 'advertising', 'fraud'] },
PSL_USER_INTERACTION: { shared: true, collect: ['analytics', 'advertising'], share: ['analytics', 'advertising'] },