@nagarjuna2002/ios-agent 0.2.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/dist/config.js ADDED
@@ -0,0 +1,82 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { LAYOUT_VERSION } from "./layout.js";
4
+ export const CONFIG_VERSION = 1;
5
+ export function defaultConfig(name, appPath, now = new Date()) {
6
+ return {
7
+ layoutVersion: LAYOUT_VERSION,
8
+ configVersion: CONFIG_VERSION,
9
+ name,
10
+ createdAt: now.toISOString(),
11
+ apps: [{ name, path: toPosix(appPath), platforms: ["iOS"] }],
12
+ plugins: [],
13
+ };
14
+ }
15
+ export function writeConfig(layout, config) {
16
+ fs.mkdirSync(layout.internal, { recursive: true });
17
+ fs.writeFileSync(layout.config, `${JSON.stringify(config, null, 2)}\n`, "utf8");
18
+ }
19
+ export class ConfigError extends Error {
20
+ }
21
+ /**
22
+ * Read and validate the config.
23
+ *
24
+ * Throws with the offending file named rather than returning a partial object:
25
+ * a config that is present but malformed is a different problem from one that
26
+ * is missing, and collapsing the two sends people looking in the wrong place.
27
+ */
28
+ export function readConfig(layout) {
29
+ let raw;
30
+ try {
31
+ raw = fs.readFileSync(layout.config, "utf8");
32
+ }
33
+ catch {
34
+ throw new ConfigError(`No config at ${layout.config}. Run \`ios-agent init\` in this project.`);
35
+ }
36
+ let parsed;
37
+ try {
38
+ parsed = JSON.parse(raw);
39
+ }
40
+ catch (error) {
41
+ throw new ConfigError(`${layout.config} is not valid JSON: ${error.message}`);
42
+ }
43
+ if (typeof parsed !== "object" || parsed === null) {
44
+ throw new ConfigError(`${layout.config} must contain a JSON object.`);
45
+ }
46
+ const candidate = parsed;
47
+ if (typeof candidate.name !== "string" || candidate.name.length === 0) {
48
+ throw new ConfigError(`${layout.config} is missing a "name".`);
49
+ }
50
+ if (!Array.isArray(candidate.apps)) {
51
+ throw new ConfigError(`${layout.config} is missing an "apps" array.`);
52
+ }
53
+ const layoutVersion = typeof candidate.layoutVersion === "number" ? candidate.layoutVersion : 0;
54
+ if (layoutVersion > LAYOUT_VERSION) {
55
+ throw new ConfigError(`${layout.config} was written by a newer ios-agent (layout ${layoutVersion}, this build understands ${LAYOUT_VERSION}). Upgrade rather than letting an old build rewrite it.`);
56
+ }
57
+ return {
58
+ layoutVersion,
59
+ configVersion: typeof candidate.configVersion === "number" ? candidate.configVersion : 0,
60
+ name: candidate.name,
61
+ createdAt: typeof candidate.createdAt === "string" ? candidate.createdAt : "",
62
+ apps: candidate.apps.filter(isAppEntry).map((app) => ({ ...app, path: toPosix(app.path) })),
63
+ plugins: Array.isArray(candidate.plugins) ? candidate.plugins.filter((p) => typeof p === "string") : [],
64
+ };
65
+ }
66
+ function isAppEntry(value) {
67
+ if (typeof value !== "object" || value === null)
68
+ return false;
69
+ const entry = value;
70
+ return typeof entry.name === "string" && typeof entry.path === "string";
71
+ }
72
+ /**
73
+ * Store paths POSIX-style regardless of the platform that wrote them.
74
+ *
75
+ * A Windows-authored config carrying `App\MyApp` is unreadable on the macOS
76
+ * machine that builds the app — and this file is tracked, so it crosses
77
+ * machines by design.
78
+ */
79
+ export function toPosix(value) {
80
+ return value.split(path.sep).join("/");
81
+ }
82
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,cAAc,EAAiB,MAAM,aAAa,CAAC;AAmC5D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAAe,EAAE,MAAY,IAAI,IAAI,EAAE;IACjF,OAAO;QACL,aAAa,EAAE,cAAc;QAC7B,aAAa,EAAE,cAAc;QAC7B,IAAI;QACJ,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE;QAC5B,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,EAAE;KACZ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAqB,EAAE,MAAqB;IACtE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,OAAO,WAAY,SAAQ,KAAK;CAAG;AAEzC;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,MAAqB;IAC9C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,WAAW,CAAC,gBAAgB,MAAM,CAAC,MAAM,2CAA2C,CAAC,CAAC;IAClG,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,CAAC,MAAM,uBAAwB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAClD,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,CAAC,MAAM,8BAA8B,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,SAAS,GAAG,MAAgC,CAAC;IACnD,IAAI,OAAO,SAAS,CAAC,IAAI,KAAK,QAAQ,IAAI,SAAS,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,CAAC,MAAM,uBAAuB,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,CAAC,MAAM,8BAA8B,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,aAAa,GAAG,OAAO,SAAS,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IAChG,IAAI,aAAa,GAAG,cAAc,EAAE,CAAC;QACnC,MAAM,IAAI,WAAW,CACnB,GAAG,MAAM,CAAC,MAAM,6CAA6C,aAAa,4BAA4B,cAAc,yDAAyD,CAC9K,CAAC;IACJ,CAAC;IAED,OAAO;QACL,aAAa;QACb,aAAa,EAAE,OAAO,SAAS,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACxF,IAAI,EAAE,SAAS,CAAC,IAAI;QACpB,SAAS,EAAE,OAAO,SAAS,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;QAC7E,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3F,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;KACxG,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,KAAK,GAAG,KAA0B,CAAC;IACzC,OAAO,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;AAC1E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { ProjectLayout } from "./layout.js";
2
+ export interface Discovery {
3
+ readonly layout: ProjectLayout;
4
+ /** How the root was determined — surfaced by `where`, because an implicit root is unfalsifiable. */
5
+ readonly source: "explicit" | "environment" | "ancestor";
6
+ }
7
+ /**
8
+ * Find the project root for a working directory.
9
+ *
10
+ * Resolution order, highest first:
11
+ * 1. an explicit path the caller passed
12
+ * 2. `IOS_AGENT_HOME`
13
+ * 3. the nearest ancestor containing `.ios-agent/`
14
+ *
15
+ * The upward walk is what git, npm, and cargo all do, and it is why `ios-agent`
16
+ * works from a nested source directory instead of only from the root. Returning
17
+ * `source` alongside the path matters as much as the path: a user staring at an
18
+ * empty result cannot otherwise tell an empty project from a wrong root, which
19
+ * is the single most confusing failure this class of tool has.
20
+ */
21
+ export declare function discoverProject(startDir?: string, env?: NodeJS.ProcessEnv, explicitRoot?: string): Discovery | undefined;
@@ -0,0 +1,48 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { INTERNAL_DIR, layoutFor } from "./layout.js";
4
+ /** How far up the tree to look before giving up. Guards against a symlink loop. */
5
+ const MAX_ASCENT = 64;
6
+ /**
7
+ * Find the project root for a working directory.
8
+ *
9
+ * Resolution order, highest first:
10
+ * 1. an explicit path the caller passed
11
+ * 2. `IOS_AGENT_HOME`
12
+ * 3. the nearest ancestor containing `.ios-agent/`
13
+ *
14
+ * The upward walk is what git, npm, and cargo all do, and it is why `ios-agent`
15
+ * works from a nested source directory instead of only from the root. Returning
16
+ * `source` alongside the path matters as much as the path: a user staring at an
17
+ * empty result cannot otherwise tell an empty project from a wrong root, which
18
+ * is the single most confusing failure this class of tool has.
19
+ */
20
+ export function discoverProject(startDir = process.cwd(), env = process.env, explicitRoot) {
21
+ if (explicitRoot) {
22
+ return { layout: layoutFor(explicitRoot), source: "explicit" };
23
+ }
24
+ const fromEnv = env.IOS_AGENT_HOME?.trim();
25
+ if (fromEnv) {
26
+ return { layout: layoutFor(fromEnv), source: "environment" };
27
+ }
28
+ let current = path.resolve(startDir);
29
+ for (let step = 0; step < MAX_ASCENT; step += 1) {
30
+ if (isDirectory(path.join(current, INTERNAL_DIR))) {
31
+ return { layout: layoutFor(current), source: "ancestor" };
32
+ }
33
+ const parent = path.dirname(current);
34
+ if (parent === current)
35
+ break;
36
+ current = parent;
37
+ }
38
+ return undefined;
39
+ }
40
+ function isDirectory(candidate) {
41
+ try {
42
+ return fs.statSync(candidate).isDirectory();
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAiB,SAAS,EAAE,MAAM,aAAa,CAAC;AAErE,mFAAmF;AACnF,MAAM,UAAU,GAAG,EAAE,CAAC;AAQtB;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAC7B,WAAmB,OAAO,CAAC,GAAG,EAAE,EAChC,MAAyB,OAAO,CAAC,GAAG,EACpC,YAAqB;IAErB,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACjE,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAC/D,CAAC;IAED,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,UAAU,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;QAChD,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;YAClD,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;QAC5D,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,MAAM,KAAK,OAAO;YAAE,MAAM;QAC9B,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,WAAW,CAAC,SAAiB;IACpC,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { run } from "./commands.js";
3
+ process.exitCode = run(process.argv.slice(2));
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEpC,OAAO,CAAC,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The one place the internal directory is named.
3
+ *
4
+ * Every other module derives from this table. A string literal ".ios-agent"
5
+ * anywhere else in the codebase is a bug: it is how a rename half-lands and the
6
+ * CLI starts writing to one directory while the MCP server reads another.
7
+ */
8
+ export declare const INTERNAL_DIR = ".ios-agent";
9
+ /** The user-facing source directory. The only thing they should need to open. */
10
+ export declare const APP_DIR = "App";
11
+ /** Bumped when the on-disk shape changes incompatibly. Stored in config.json. */
12
+ export declare const LAYOUT_VERSION = 1;
13
+ export type EntryKind = "file" | "directory";
14
+ export interface InternalEntry {
15
+ readonly name: string;
16
+ readonly kind: EntryKind;
17
+ /**
18
+ * Whether the entry belongs in version control.
19
+ *
20
+ * This single flag drives two things that must never disagree: the generated
21
+ * `.ios-agent/.gitignore`, and what `ios-agent clean` is allowed to delete.
22
+ * Deriving both from one declaration is why `clean` can be non-interactive —
23
+ * it provably cannot remove something a human authored.
24
+ */
25
+ readonly tracked: boolean;
26
+ readonly purpose: string;
27
+ /** Created eagerly on scaffold, or lazily on first use. */
28
+ readonly eager: boolean;
29
+ }
30
+ /**
31
+ * Everything the tool owns, and nothing the user does.
32
+ *
33
+ * Adding a future feature — plugins, simulator state, build artifacts — is one
34
+ * row here. It then appears in the gitignore, in `clean`, in `doctor`, and in
35
+ * `where --json` with no further edits. That is the scalability requirement
36
+ * expressed as code rather than as a convention people have to remember.
37
+ */
38
+ export declare const INTERNAL_ENTRIES: readonly InternalEntry[];
39
+ export interface ProjectLayout {
40
+ /** Directory containing `App/` and `.ios-agent/`. */
41
+ readonly root: string;
42
+ readonly app: string;
43
+ readonly internal: string;
44
+ readonly config: string;
45
+ readonly state: string;
46
+ readonly metadata: string;
47
+ readonly cache: string;
48
+ readonly logs: string;
49
+ readonly build: string;
50
+ readonly screenshots: string;
51
+ readonly templates: string;
52
+ readonly plugins: string;
53
+ readonly tmp: string;
54
+ readonly gitignore: string;
55
+ }
56
+ /**
57
+ * Absolute paths for one project root.
58
+ *
59
+ * Pure — it touches no filesystem, so it is the same on every platform and
60
+ * trivially testable. Creation is a separate, explicit step.
61
+ */
62
+ export declare function layoutFor(root: string): ProjectLayout;
63
+ /**
64
+ * The contents of `.ios-agent/.gitignore`, generated from `INTERNAL_ENTRIES`.
65
+ *
66
+ * Ignore-everything-then-unignore keeps the rules inside the directory they
67
+ * govern, so the project's root `.gitignore` stays about the project. It also
68
+ * means a future disposable directory is ignored the moment it is declared —
69
+ * the common failure being a new cache directory that nobody remembers to add,
70
+ * and which then shows up in someone's pull request.
71
+ */
72
+ export declare function gitignoreContents(): string;
73
+ /** Entries `clean` may delete: exactly those not under version control. */
74
+ export declare function disposableEntries(): readonly InternalEntry[];
75
+ /**
76
+ * Where caches shared across every project go.
77
+ *
78
+ * Per-project caches that hold downloaded templates or SDK metadata make a
79
+ * fresh clone slow and duplicate hundreds of megabytes per project — the reason
80
+ * npm, Cargo, and pub all keep one user-level store. Each platform has its own
81
+ * documented location and none of them is `~/.ios-agent`.
82
+ */
83
+ /**
84
+ * `platform` and `home` are parameters so tests can exercise all three branches
85
+ * on one machine. They do NOT make the result platform-independent: `path` uses
86
+ * the **host's** rules throughout, so asking for the linux branch on Windows
87
+ * returns a Windows-shaped string. Production never does that — the defaults
88
+ * are the host — but a test that hardcodes an expected string will pass on
89
+ * Linux and fail on Windows.
90
+ *
91
+ * Concretely: `path.resolve("/xdg")` yields `/xdg` on Linux and `D:\xdg` on a
92
+ * Windows runner, because resolve anchors a drive-less path to the current
93
+ * drive. Expectations must therefore be built with the same call this function
94
+ * makes, not with a literal.
95
+ */
96
+ export declare function globalCacheDir(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, home?: string): string;
package/dist/layout.js ADDED
@@ -0,0 +1,184 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ /**
4
+ * The one place the internal directory is named.
5
+ *
6
+ * Every other module derives from this table. A string literal ".ios-agent"
7
+ * anywhere else in the codebase is a bug: it is how a rename half-lands and the
8
+ * CLI starts writing to one directory while the MCP server reads another.
9
+ */
10
+ export const INTERNAL_DIR = ".ios-agent";
11
+ /** The user-facing source directory. The only thing they should need to open. */
12
+ export const APP_DIR = "App";
13
+ /** Bumped when the on-disk shape changes incompatibly. Stored in config.json. */
14
+ export const LAYOUT_VERSION = 1;
15
+ /**
16
+ * Everything the tool owns, and nothing the user does.
17
+ *
18
+ * Adding a future feature — plugins, simulator state, build artifacts — is one
19
+ * row here. It then appears in the gitignore, in `clean`, in `doctor`, and in
20
+ * `where --json` with no further edits. That is the scalability requirement
21
+ * expressed as code rather than as a convention people have to remember.
22
+ */
23
+ export const INTERNAL_ENTRIES = [
24
+ {
25
+ name: "config.json",
26
+ kind: "file",
27
+ tracked: true,
28
+ eager: true,
29
+ purpose: "Project identity and settings. The only tracked file here — it describes the project, so it belongs in review.",
30
+ },
31
+ {
32
+ name: "state.json",
33
+ kind: "file",
34
+ tracked: false,
35
+ eager: false,
36
+ purpose: "Mutable runtime state: last simulator, last build, session scratch. Machine-local by definition.",
37
+ },
38
+ {
39
+ name: "metadata.json",
40
+ kind: "file",
41
+ tracked: false,
42
+ eager: false,
43
+ purpose: "Facts derived by scanning the project. Always reproducible, never authored.",
44
+ },
45
+ {
46
+ name: "cache",
47
+ kind: "directory",
48
+ tracked: false,
49
+ eager: false,
50
+ purpose: "Project-derived cache. Anything shared across projects belongs in the global cache instead.",
51
+ },
52
+ {
53
+ name: "logs",
54
+ kind: "directory",
55
+ tracked: false,
56
+ eager: false,
57
+ purpose: "Command and build logs, newest last.",
58
+ },
59
+ {
60
+ name: "build",
61
+ kind: "directory",
62
+ tracked: false,
63
+ eager: false,
64
+ purpose: "Derived build artifacts. Deleting this must never lose work.",
65
+ },
66
+ {
67
+ name: "screenshots",
68
+ kind: "directory",
69
+ tracked: false,
70
+ eager: false,
71
+ purpose: "Simulator captures from automated runs.",
72
+ },
73
+ {
74
+ name: "templates",
75
+ kind: "directory",
76
+ tracked: true,
77
+ eager: false,
78
+ purpose: "Project-local template overrides. Tracked, because an override is a decision the team made.",
79
+ },
80
+ {
81
+ name: "plugins",
82
+ kind: "directory",
83
+ tracked: true,
84
+ eager: false,
85
+ purpose: "Plugin manifests. Tracked so a checkout reproduces the toolchain; downloaded plugin code lives in the global cache.",
86
+ },
87
+ {
88
+ name: "tmp",
89
+ kind: "directory",
90
+ tracked: false,
91
+ eager: false,
92
+ purpose: "Scratch space for in-flight commands. Safe to delete at any moment.",
93
+ },
94
+ ];
95
+ /**
96
+ * Absolute paths for one project root.
97
+ *
98
+ * Pure — it touches no filesystem, so it is the same on every platform and
99
+ * trivially testable. Creation is a separate, explicit step.
100
+ */
101
+ export function layoutFor(root) {
102
+ const absoluteRoot = path.resolve(root);
103
+ const internal = path.join(absoluteRoot, INTERNAL_DIR);
104
+ const inside = (name) => path.join(internal, name);
105
+ return {
106
+ root: absoluteRoot,
107
+ app: path.join(absoluteRoot, APP_DIR),
108
+ internal,
109
+ config: inside("config.json"),
110
+ state: inside("state.json"),
111
+ metadata: inside("metadata.json"),
112
+ cache: inside("cache"),
113
+ logs: inside("logs"),
114
+ build: inside("build"),
115
+ screenshots: inside("screenshots"),
116
+ templates: inside("templates"),
117
+ plugins: inside("plugins"),
118
+ tmp: inside("tmp"),
119
+ gitignore: inside(".gitignore"),
120
+ };
121
+ }
122
+ /**
123
+ * The contents of `.ios-agent/.gitignore`, generated from `INTERNAL_ENTRIES`.
124
+ *
125
+ * Ignore-everything-then-unignore keeps the rules inside the directory they
126
+ * govern, so the project's root `.gitignore` stays about the project. It also
127
+ * means a future disposable directory is ignored the moment it is declared —
128
+ * the common failure being a new cache directory that nobody remembers to add,
129
+ * and which then shows up in someone's pull request.
130
+ */
131
+ export function gitignoreContents() {
132
+ const tracked = INTERNAL_ENTRIES.filter((entry) => entry.tracked);
133
+ const lines = [
134
+ "# Generated by ios-agent. Edits are overwritten.",
135
+ "#",
136
+ "# Everything the tool writes is disposable and machine-local; the few",
137
+ "# entries below describe the project itself and belong in review.",
138
+ "*",
139
+ "!.gitignore",
140
+ ...tracked.map((entry) => (entry.kind === "directory" ? `!${entry.name}/` : `!${entry.name}`)),
141
+ "",
142
+ ];
143
+ return lines.join("\n");
144
+ }
145
+ /** Entries `clean` may delete: exactly those not under version control. */
146
+ export function disposableEntries() {
147
+ return INTERNAL_ENTRIES.filter((entry) => !entry.tracked);
148
+ }
149
+ /**
150
+ * Where caches shared across every project go.
151
+ *
152
+ * Per-project caches that hold downloaded templates or SDK metadata make a
153
+ * fresh clone slow and duplicate hundreds of megabytes per project — the reason
154
+ * npm, Cargo, and pub all keep one user-level store. Each platform has its own
155
+ * documented location and none of them is `~/.ios-agent`.
156
+ */
157
+ /**
158
+ * `platform` and `home` are parameters so tests can exercise all three branches
159
+ * on one machine. They do NOT make the result platform-independent: `path` uses
160
+ * the **host's** rules throughout, so asking for the linux branch on Windows
161
+ * returns a Windows-shaped string. Production never does that — the defaults
162
+ * are the host — but a test that hardcodes an expected string will pass on
163
+ * Linux and fail on Windows.
164
+ *
165
+ * Concretely: `path.resolve("/xdg")` yields `/xdg` on Linux and `D:\xdg` on a
166
+ * Windows runner, because resolve anchors a drive-less path to the current
167
+ * drive. Expectations must therefore be built with the same call this function
168
+ * makes, not with a literal.
169
+ */
170
+ export function globalCacheDir(env = process.env, platform = process.platform, home = os.homedir()) {
171
+ const override = env.IOS_AGENT_CACHE_DIR?.trim();
172
+ if (override)
173
+ return path.resolve(override);
174
+ if (platform === "win32") {
175
+ const base = env.LOCALAPPDATA?.trim() || path.join(home, "AppData", "Local");
176
+ return path.join(base, "ios-agent", "Cache");
177
+ }
178
+ if (platform === "darwin") {
179
+ return path.join(home, "Library", "Caches", "ios-agent");
180
+ }
181
+ const xdg = env.XDG_CACHE_HOME?.trim();
182
+ return path.join(xdg ? path.resolve(xdg) : path.join(home, ".cache"), "ios-agent");
183
+ }
184
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { ProjectLayout } from "./layout.js";
2
+ export declare class ScaffoldError extends Error {
3
+ }
4
+ /**
5
+ * Validate a project name before any directory exists.
6
+ *
7
+ * Stricter than the filesystem on purpose. The name becomes a Swift type
8
+ * prefix, a directory, and a target name, so the intersection of what all three
9
+ * accept is what is actually usable — and a name rejected up front costs
10
+ * nothing, while one rejected by Xcode two steps later costs a rebuild.
11
+ */
12
+ export declare function validateProjectName(name: string): void;
13
+ export interface ScaffoldOptions {
14
+ readonly name: string;
15
+ /** Parent directory. The project is created at `<parentDir>/<name>`. */
16
+ readonly parentDir: string;
17
+ /** Create only `App/`; `.ios-agent/` materialises on first command that needs it. */
18
+ readonly minimal?: boolean;
19
+ readonly license?: "MIT" | "none";
20
+ /** Allow scaffolding into a directory that already has contents. */
21
+ readonly force?: boolean;
22
+ readonly now?: Date;
23
+ readonly brief?: string;
24
+ readonly xcodegen?: boolean;
25
+ }
26
+ export interface ScaffoldResult {
27
+ readonly layout: ProjectLayout;
28
+ /** Absolute paths written, in creation order. */
29
+ readonly created: string[];
30
+ }
31
+ export declare function scaffoldProject(options: ScaffoldOptions): ScaffoldResult;
32
+ /**
33
+ * Create `.ios-agent/` and the entries marked eager, idempotently.
34
+ *
35
+ * Every command that writes calls this first, which is what makes the minimal
36
+ * scaffold honest: `MyApp/App/` alone is a real project, and the internal
37
+ * directory appears the first time there is something to put in it rather than
38
+ * sitting empty as a promise.
39
+ */
40
+ export declare function ensureInternal(layout: ProjectLayout): string[];
41
+ /** Create one internal directory on demand. Callers name it via the layout. */
42
+ export declare function ensureInternalDir(layout: ProjectLayout, target: string): string;