@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/README.md +167 -0
- package/dist/commands.d.ts +37 -0
- package/dist/commands.js +553 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.js +82 -0
- package/dist/config.js.map +1 -0
- package/dist/discover.d.ts +21 -0
- package/dist/discover.js +48 -0
- package/dist/discover.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +96 -0
- package/dist/layout.js +184 -0
- package/dist/layout.js.map +1 -0
- package/dist/scaffold.d.ts +42 -0
- package/dist/scaffold.js +402 -0
- package/dist/scaffold.js.map +1 -0
- package/package.json +41 -0
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":"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"}
|
|
@@ -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;
|
package/dist/discover.js
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -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"}
|
package/dist/layout.d.ts
ADDED
|
@@ -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;
|