Skip to content

Commit 34cb5a3

Browse files
fix(setup): pull simulations by their single-type alias in the wizard (#92)
## Value **V.A.L.U.E. tier:** micro. It restores intended behavior in about 50 changed lines across 4 files, and a deterministic test proves it. - **Problem:** in the interactive `npm run setup` wizard, choosing simulations fails with "Single-resource pull requires exactly one resource type". Setup stops before simulations, suites and evals are downloaded, and the org folder is left half set up. It happens to anyone whose org has simulations and who picks "All". - **Who it affects:** new users connecting an org through the wizard, including everyone who runs [`npx create-vapi-app`](https://www.npmjs.com/package/create-vapi-app), which hands off to it. Non-interactive setup (`npm run setup -- <org>`) does one full pull and was never affected. ## How it works The wizard pulls what you pick one type at a time: `pull.ts <org> --force --type <type> --id …`. It passed the internal type name. But `simulations` is also the `--type` group alias for `personalities`, `scenarios`, `simulations` and `simulationSuites`, and a pull by `--id` refuses more than one type. - **`src/resource-type-args.ts` (new, config-free):** the group and path alias maps and `resolveResourceTypes` move here from `config.ts` unchanged, so tests can import them. `config.ts` parses argv and exits on import, so tests couldn't. A new `typeArgFor(type)` returns the `--type` spelling that resolves to exactly that type; for `simulations` that's `simulations/tests`. - **`src/setup.ts`:** the wizard's per-type pull passes `typeArgFor(typeKey)`. - **`tests/resource-type-args.test.ts`:** checks that every resource type comes back as exactly itself through `typeArgFor`, so a future type that collides with a group alias fails CI. It also checks that `--type simulations` still selects all four types for `apply`. ## Verification **Before:** on `main`, run the exact command the wizard builds against an org with simulations: ``` tsx src/pull.ts <org> --force --skip-bindings --type simulations --id <sim-id> ❌ Pull failed: Single-resource pull requires exactly one resource type. ``` **After:** run it with `typeArgFor("simulations")`, which gives `--type simulations/tests`: ``` Found 2 matching simulations in Vapi (requested 2) ✨ … -> resources/<org>/simulations/tests/….yml simulations: 2 new, 0 updated ``` The wizard's per-type pulls for `simulationSuites` and `evals` also completed, scoped to the requested ids. Your run had never reached them. These were read-only pulls against a real org, into a throwaway copy of the repo. `npm run build` passes, and `npm test` passes 584/584. ## Not in this PR Each per-type pull in the wizard prints the `--force` warning ("run `npm run pull` (no flag) first…") and waits 2 seconds. For an org with all 9 types, that's 9 warnings and about 18 seconds of waiting, for advice aimed at hand-run `--force` pulls. It could be a follow-up to suppress the warning for the wizard's own pulls. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents 1e96134 + 26db053 commit 34cb5a3

4 files changed

Lines changed: 110 additions & 41 deletions

File tree

‎src/config.ts‎

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ignorePatternsRead,
88
matchesIgnore as matchesIgnorePatterns,
99
} from "./resource-parse.ts";
10-
import { VALID_RESOURCE_TYPES } from "./types.ts";
10+
import { resolveResourceTypes, VALID_TYPE_ARGS } from "./resource-type-args.ts";
1111

1212
// ─────────────────────────────────────────────────────────────────────────────
1313
// CLI Argument Parsing
@@ -19,23 +19,8 @@ export interface ApplyFilter {
1919
resourceIds?: string[]; // Pull only specific remote resource IDs
2020
}
2121

22-
// Group aliases: expand a shorthand into multiple resource types
23-
const RESOURCE_GROUP_MAP: Record<string, ResourceType[]> = {
24-
simulations: [
25-
"personalities",
26-
"scenarios",
27-
"simulations",
28-
"simulationSuites",
29-
],
30-
};
31-
32-
// Path-based aliases: folder paths to resource types
33-
const RESOURCE_PATH_MAP: Record<string, ResourceType> = {
34-
"simulations/personalities": "personalities",
35-
"simulations/scenarios": "scenarios",
36-
"simulations/tests": "simulations",
37-
"simulations/suites": "simulationSuites",
38-
};
22+
// The --type aliases (groups like "simulations", paths like
23+
// "simulations/tests") live in resource-type-args.ts.
3924

4025
const SLUG_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
4126

@@ -67,28 +52,6 @@ function parseEnvironment(): Environment {
6752
return envArg;
6853
}
6954

70-
// Resolve a type argument into resource types (handles groups, paths, and direct types)
71-
function resolveResourceTypes(arg: string): ResourceType[] | null {
72-
// Check group aliases first (e.g., "simulations" → all 4 simulation types)
73-
if (RESOURCE_GROUP_MAP[arg]) {
74-
return RESOURCE_GROUP_MAP[arg];
75-
}
76-
// Check path-based aliases (e.g., "simulations/personalities" → ["personalities"])
77-
if (RESOURCE_PATH_MAP[arg]) {
78-
return [RESOURCE_PATH_MAP[arg]];
79-
}
80-
// Check direct resource type
81-
if (VALID_RESOURCE_TYPES.includes(arg as ResourceType)) {
82-
return [arg as ResourceType];
83-
}
84-
return null;
85-
}
86-
87-
const VALID_TYPE_ARGS = [
88-
...VALID_RESOURCE_TYPES,
89-
...Object.keys(RESOURCE_GROUP_MAP),
90-
...Object.keys(RESOURCE_PATH_MAP),
91-
];
9255

9356
function parseFlags(): {
9457
forceDelete: boolean;

‎src/resource-type-args.ts‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// How a `--type` argument maps to resource types. Config-free on purpose
2+
// (like api-key.ts): config.ts parses argv and exits at import time, so the
3+
// mapping lives here where setup.ts and tests can import it.
4+
5+
import type { ResourceType } from "./types.ts";
6+
import { VALID_RESOURCE_TYPES } from "./types.ts";
7+
8+
// Group aliases: expand a shorthand into multiple resource types
9+
const RESOURCE_GROUP_MAP: Record<string, ResourceType[]> = {
10+
simulations: [
11+
"personalities",
12+
"scenarios",
13+
"simulations",
14+
"simulationSuites",
15+
],
16+
};
17+
18+
// Path-based aliases: folder paths to resource types
19+
const RESOURCE_PATH_MAP: Record<string, ResourceType> = {
20+
"simulations/personalities": "personalities",
21+
"simulations/scenarios": "scenarios",
22+
"simulations/tests": "simulations",
23+
"simulations/suites": "simulationSuites",
24+
};
25+
26+
// Resolve a type argument into resource types (handles groups, paths, and direct types)
27+
export function resolveResourceTypes(arg: string): ResourceType[] | null {
28+
// Check group aliases first (e.g., "simulations" → all 4 simulation types)
29+
if (RESOURCE_GROUP_MAP[arg]) {
30+
return RESOURCE_GROUP_MAP[arg];
31+
}
32+
// Check path-based aliases (e.g., "simulations/personalities" → ["personalities"])
33+
if (RESOURCE_PATH_MAP[arg]) {
34+
return [RESOURCE_PATH_MAP[arg]];
35+
}
36+
// Check direct resource type
37+
if (VALID_RESOURCE_TYPES.includes(arg as ResourceType)) {
38+
return [arg as ResourceType];
39+
}
40+
return null;
41+
}
42+
43+
export const VALID_TYPE_ARGS = [
44+
...VALID_RESOURCE_TYPES,
45+
...Object.keys(RESOURCE_GROUP_MAP),
46+
...Object.keys(RESOURCE_PATH_MAP),
47+
];
48+
49+
/**
50+
* The `--type` argument that names exactly one resource type. Usually the
51+
* type itself, but `simulations` is also the group alias for all four
52+
* simulation types, so the single type is spelled by its folder path,
53+
* `simulations/tests`. Use this whenever a command builds a `--type` for one
54+
* type, e.g. a single-resource pull with `--id`.
55+
*/
56+
export function typeArgFor(type: string): string {
57+
if (!(type in RESOURCE_GROUP_MAP)) return type;
58+
const pathAlias = Object.keys(RESOURCE_PATH_MAP).find(
59+
(path) => RESOURCE_PATH_MAP[path] === type,
60+
);
61+
return pathAlias ?? type;
62+
}

‎src/setup.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
resolveOrgApiKey,
1313
} from "./api-key.ts";
1414
import { updateEnvConnection } from "./bindings.ts";
15+
import { typeArgFor } from "./resource-type-args.ts";
1516
import searchableCheckbox, { BACK_SENTINEL } from "./searchableCheckbox.js";
1617
import {
1718
type DirectSetupOptions,
@@ -348,7 +349,9 @@ function invokePull(
348349
"--force",
349350
"--skip-bindings",
350351
"--type",
351-
typeKey,
352+
// Not typeKey itself: "simulations" is also the group alias for all
353+
// four simulation types, which a pull by --id refuses.
354+
typeArgFor(typeKey),
352355
...idArgs,
353356
].join(" ");
354357
execSync(cmd, { cwd: BASE_DIR, stdio: "inherit", env });

‎tests/resource-type-args.test.ts‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
import {
4+
resolveResourceTypes,
5+
typeArgFor,
6+
} from "../src/resource-type-args.ts";
7+
import { VALID_RESOURCE_TYPES } from "../src/types.ts";
8+
9+
// The setup wizard downloads what you pick one type at a time, with
10+
// `pull.ts <org> --type <type> --id …`, and a pull by --id accepts exactly one
11+
// type. It passed the internal type name, and "simulations" is also the
12+
// group alias for all four simulation types, so choosing simulations in the
13+
// wizard failed with "Single-resource pull requires exactly one resource
14+
// type" and stopped setup partway.
15+
16+
test("typeArgFor names exactly one type for every resource type", () => {
17+
for (const type of VALID_RESOURCE_TYPES) {
18+
assert.deepEqual(resolveResourceTypes(typeArgFor(type)), [type], type);
19+
}
20+
});
21+
22+
test("typeArgFor spells simulations by its folder path", () => {
23+
assert.equal(typeArgFor("simulations"), "simulations/tests");
24+
});
25+
26+
test("typeArgFor leaves other types unchanged", () => {
27+
assert.equal(typeArgFor("assistants"), "assistants");
28+
});
29+
30+
test("--type simulations still selects every simulation type", () => {
31+
assert.deepEqual(resolveResourceTypes("simulations"), [
32+
"personalities",
33+
"scenarios",
34+
"simulations",
35+
"simulationSuites",
36+
]);
37+
});
38+
39+
test("resolveResourceTypes rejects an unknown type", () => {
40+
assert.equal(resolveResourceTypes("widgets"), null);
41+
});

0 commit comments

Comments
 (0)