@shipeasy/openapi 1.5.0 → 2.1.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 +37 -0
- package/dist/chunk-EPLLHLZN.js +1616 -0
- package/dist/chunk-EPLLHLZN.js.map +1 -0
- package/dist/chunk-SPH5HWCD.js +3326 -0
- package/dist/chunk-SPH5HWCD.js.map +1 -0
- package/dist/client-pVkoo3Bf.d.ts +11186 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +215 -0
- package/dist/client.js.map +1 -0
- package/dist/custom.d.ts +64 -0
- package/dist/custom.js +292 -0
- package/dist/custom.js.map +1 -0
- package/dist/index.d.ts +29 -17
- package/dist/index.js +253 -2458
- package/dist/index.js.map +1 -0
- package/dist/node-context.d.ts +85 -0
- package/dist/node-context.js +110 -0
- package/dist/node-context.js.map +1 -0
- package/dist/schemas.d.ts +7920 -0
- package/dist/schemas.js +1083 -0
- package/dist/schemas.js.map +1 -0
- package/openapi.json +15297 -3044
- package/openapi.yaml +13979 -0
- package/package.json +37 -21
- package/spec/components/errors.yaml +53 -0
- package/spec/components/parameters.yaml +24 -0
- package/spec/components/responses.yaml +107 -0
- package/spec/components/schemas/alert-rules.yaml +175 -0
- package/spec/components/schemas/attributes.yaml +157 -0
- package/spec/components/schemas/common.yaml +87 -0
- package/spec/components/schemas/configs.yaml +371 -0
- package/spec/components/schemas/connectors.yaml +593 -0
- package/spec/components/schemas/errors-api.yaml +209 -0
- package/spec/components/schemas/events.yaml +321 -0
- package/spec/components/schemas/experiments.yaml +865 -0
- package/spec/components/schemas/gate-templates.yaml +198 -0
- package/spec/components/schemas/gates.yaml +890 -0
- package/spec/components/schemas/i18n.yaml +478 -0
- package/spec/components/schemas/keys.yaml +186 -0
- package/spec/components/schemas/killswitches.yaml +309 -0
- package/spec/components/schemas/metrics.yaml +547 -0
- package/spec/components/schemas/ops.yaml +1011 -0
- package/spec/components/schemas/projects.yaml +222 -0
- package/spec/components/schemas/search.yaml +46 -0
- package/spec/components/schemas/universes.yaml +127 -0
- package/spec/openapi.yaml +348 -0
- package/spec/paths/alert-rules.yaml +250 -0
- package/spec/paths/attributes.yaml +261 -0
- package/spec/paths/configs.yaml +744 -0
- package/spec/paths/connectors.yaml +748 -0
- package/spec/paths/errors.yaml +382 -0
- package/spec/paths/events.yaml +366 -0
- package/spec/paths/experiments.yaml +777 -0
- package/spec/paths/gate-templates.yaml +263 -0
- package/spec/paths/gates.yaml +451 -0
- package/spec/paths/i18n.yaml +544 -0
- package/spec/paths/keys.yaml +181 -0
- package/spec/paths/killswitches.yaml +557 -0
- package/spec/paths/metrics.yaml +302 -0
- package/spec/paths/ops.yaml +446 -0
- package/spec/paths/projects.yaml +210 -0
- package/spec/paths/search.yaml +62 -0
- package/spec/paths/universes.yaml +247 -0
- package/dist/resources/alert-rules.d.ts +0 -50
- package/dist/resources/alert-rules.js +0 -75
- package/dist/resources/configs.d.ts +0 -681
- package/dist/resources/configs.js +0 -509
- package/dist/resources/experiments.d.ts +0 -849
- package/dist/resources/experiments.js +0 -707
- package/dist/resources/gates.d.ts +0 -808
- package/dist/resources/gates.js +0 -522
- package/dist/resources/index.d.ts +0 -2749
- package/dist/resources/index.js +0 -2391
- package/dist/resources/killswitches.d.ts +0 -436
- package/dist/resources/killswitches.js +0 -514
- package/dist/resources/universes.d.ts +0 -185
- package/dist/resources/universes.js +0 -234
- package/dist/schemas/alert-rules.d.ts +0 -65
- package/dist/schemas/alert-rules.js +0 -37
- package/dist/schemas/configs.d.ts +0 -154
- package/dist/schemas/configs.js +0 -134
- package/dist/schemas/constants.d.ts +0 -6
- package/dist/schemas/constants.js +0 -7
- package/dist/schemas/experiments.d.ts +0 -229
- package/dist/schemas/experiments.js +0 -221
- package/dist/schemas/folder.d.ts +0 -10
- package/dist/schemas/folder.js +0 -8
- package/dist/schemas/gates.d.ts +0 -386
- package/dist/schemas/gates.js +0 -190
- package/dist/schemas/index.d.ts +0 -11
- package/dist/schemas/index.js +0 -762
- package/dist/schemas/killswitches.d.ts +0 -110
- package/dist/schemas/killswitches.js +0 -226
- package/dist/schemas/metric-name.d.ts +0 -5
- package/dist/schemas/metric-name.js +0 -6
- package/dist/schemas/pagination.d.ts +0 -37
- package/dist/schemas/pagination.js +0 -44
- package/dist/schemas/universes.d.ts +0 -35
- package/dist/schemas/universes.js +0 -55
- package/dist/transport.d.ts +0 -30
- package/dist/transport.js +0 -44
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import { zErrorCode } from \"./generated/zod.gen.js\";\nimport type { Error as ApiErrorBody, ErrorCode } from \"./generated/types.gen.js\";\n\nexport type { ErrorCode };\nexport type { ApiErrorBody };\n\n/**\n * The canonical `ErrorCode` catalogue as a runtime array — derived from the\n * generated Zod enum so it can never drift from the spec. Mirrors\n * `@shipeasy/core`'s `ErrorCode`; the parity test (`scripts/check-drift.mjs`)\n * asserts every `x-error-codes` entry in the spec is one of these.\n */\nexport const ERROR_CODES = zErrorCode.options as readonly ErrorCode[];\n\n/** Type guard for the uniform `{ error, code?, detail? }` error envelope. */\nexport function isApiErrorBody(value: unknown): value is ApiErrorBody {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"error\" in value &&\n typeof (value as { error: unknown }).error === \"string\"\n );\n}\n\n/** Narrow an error body to a known `ErrorCode`, or `undefined` if uncatalogued. */\nexport function errorCodeOf(value: unknown): ErrorCode | undefined {\n if (!isApiErrorBody(value)) return undefined;\n const code = value.code;\n return code && (ERROR_CODES as readonly string[]).includes(code) ? code : undefined;\n}\n\n/** Which binary is reporting the failure — determines which command to tell the caller to run. */\nexport type AuthSurface = \"cli\" | \"mcp\";\n\nconst REAUTH_COMMAND: Record<AuthSurface, string> = {\n cli: \"shipeasy login --force\",\n mcp: \"shipeasy-mcp install --force\",\n};\n\n/**\n * Structured, agent-parseable remediation text for an admin-API auth failure\n * (401/403) — shared by the CLI's and MCP's error printers so every command\n * and every tool call reports auth failures the same way. The leading\n * `AUTH_REQUIRED:` / `AUTH_FORBIDDEN:` token is a stable marker a calling\n * agent can grep for; everything after it is human-readable remediation.\n *\n * Returns `undefined` for non-auth statuses so callers fall back to their\n * normal error formatting.\n */\nexport function formatAuthFailure(\n surface: AuthSurface,\n status: number,\n message: string,\n): string | undefined {\n const reauth = REAUTH_COMMAND[surface];\n if (status === 401) {\n return [\n `AUTH_REQUIRED: ${message} (401)`,\n ``,\n `Cause: the request's Shipeasy credentials are missing, invalid, or expired.`,\n `Fix:`,\n ` 1. Run: ${reauth}`,\n ` 2. Retry the command that failed.`,\n ].join(\"\\n\");\n }\n if (status === 403) {\n return [\n `AUTH_FORBIDDEN: ${message} (403)`,\n ``,\n `Cause: the credentials are valid but don't have access to this project or resource.`,\n `Fix:`,\n ` 1. Confirm the bound project is correct (check the .shipeasy file, or run: shipeasy root --json).`,\n ` 2. If it's the wrong project, run: shipeasy bind <project_id>.`,\n ` 3. If it's the right project, ask a workspace admin to grant access — retrying will not help.`,\n ].join(\"\\n\");\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYO,IAAM,cAAc,WAAW;AAG/B,SAAS,eAAe,OAAuC;AACpE,SACE,OAAO,UAAU,YACjB,UAAU,QACV,WAAW,SACX,OAAQ,MAA6B,UAAU;AAEnD;AAGO,SAAS,YAAY,OAAuC;AACjE,MAAI,CAAC,eAAe,KAAK,EAAG,QAAO;AACnC,QAAM,OAAO,MAAM;AACnB,SAAO,QAAS,YAAkC,SAAS,IAAI,IAAI,OAAO;AAC5E;AAKA,IAAM,iBAA8C;AAAA,EAClD,KAAK;AAAA,EACL,KAAK;AACP;AAYO,SAAS,kBACd,SACA,QACA,SACoB;AACpB,QAAM,SAAS,eAAe,OAAO;AACrC,MAAI,WAAW,KAAK;AAClB,WAAO;AAAA,MACL,kBAAkB,OAAO;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,MAAM;AAAA,MACnB;AAAA,IACF,EAAE,KAAK,IAAI;AAAA,EACb;AACA,MAAI,WAAW,KAAK;AAClB,WAAO;AAAA,MACL,mBAAmB,OAAO;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AAAA,EACb;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared `.shipeasy` project-binding file — the single source of truth for
|
|
3
|
+
* both `@shipeasy/cli` and `@shipeasy/mcp`. Lives under `./node-context` because
|
|
4
|
+
* it touches `node:fs`: **never import it from the worker-bundled graph** (the
|
|
5
|
+
* openapi root/`./custom` entries stay fs-free and worker-safe).
|
|
6
|
+
*
|
|
7
|
+
* Model: **one `.shipeasy` per project**. The folder that holds the file IS the
|
|
8
|
+
* project root. Reads walk up from the cwd to the *nearest* file (like git and
|
|
9
|
+
* `.git`), so a subproject's own `.shipeasy` shadows any ancestor and the walk
|
|
10
|
+
* stops there. Writes (`bindProject`, `mergeDetected`) target the LOCAL dir they
|
|
11
|
+
* are given — they never reach up to reuse an ancestor — so each onboarded
|
|
12
|
+
* subproject gets its own file.
|
|
13
|
+
*/
|
|
14
|
+
declare const FILENAME = ".shipeasy";
|
|
15
|
+
/** Current `.shipeasy` schema version. Absent in a file → treat as legacy (1). */
|
|
16
|
+
declare const SCHEMA_VERSION = 1;
|
|
17
|
+
interface ProjectConfig {
|
|
18
|
+
/** Shape version; lets future changes stay backward-compatible. */
|
|
19
|
+
schema_version?: number;
|
|
20
|
+
/**
|
|
21
|
+
* The Shipeasy project this directory pushes to. Required for any mutating
|
|
22
|
+
* CLI/MCP operation. Stops a session logged into project A from silently
|
|
23
|
+
* writing to project B because someone's cwd is wrong.
|
|
24
|
+
*/
|
|
25
|
+
project_id?: string;
|
|
26
|
+
/** Human-readable project name — display only, not authoritative. */
|
|
27
|
+
project_name?: string;
|
|
28
|
+
/** Detected language (e.g. `typescript`, `python`) — written by `detect`. */
|
|
29
|
+
language?: string;
|
|
30
|
+
/** Docs SDK handle — the value `shipeasy docs` defaults to when `--sdk` is omitted. */
|
|
31
|
+
sdk?: string;
|
|
32
|
+
/** Detected frameworks (e.g. `["nextjs", "react"]`) — written by `detect`. */
|
|
33
|
+
frameworks?: string[];
|
|
34
|
+
i18n?: {
|
|
35
|
+
client_key?: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Walk up from `dir` until a `.shipeasy` file is found, like git does for
|
|
40
|
+
* `.git`. Returns the directory containing the file, or `null` if the search
|
|
41
|
+
* hits the filesystem root without finding one.
|
|
42
|
+
*/
|
|
43
|
+
declare function findProjectConfigDir(dir: string): string | null;
|
|
44
|
+
/** Sync read of the nearest `.shipeasy` (walking up). Empty object if none. */
|
|
45
|
+
declare function readProjectConfigSync(dir: string): ProjectConfig;
|
|
46
|
+
/** Async read of the nearest `.shipeasy` (walking up). Empty object if none. */
|
|
47
|
+
declare function readProjectConfig(dir: string): Promise<ProjectConfig>;
|
|
48
|
+
/** Write `cfg` to `dir/.shipeasy` verbatim (stamping schema_version if absent). */
|
|
49
|
+
declare function writeProjectConfig(dir: string, cfg: ProjectConfig): void;
|
|
50
|
+
/** Project id from the nearest `.shipeasy` (sync). */
|
|
51
|
+
declare function getBoundProjectId(dir: string): string | undefined;
|
|
52
|
+
/** Alias kept for the MCP call sites. */
|
|
53
|
+
declare const getBoundProjectIdSync: typeof getBoundProjectId;
|
|
54
|
+
/** Docs SDK handle from the nearest `.shipeasy` (sync) — the `docs` default. */
|
|
55
|
+
declare function getBoundSdk(dir: string): string | undefined;
|
|
56
|
+
declare function getI18nClientKey(dir: string): string | undefined;
|
|
57
|
+
declare function saveI18nClientKey(dir: string, key: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Bind the LOCAL directory `dir` to `projectId` — writes `dir/.shipeasy`
|
|
60
|
+
* directly (create, or merge over an existing LOCAL file to preserve `sdk` /
|
|
61
|
+
* `i18n` / detection fields). It does NOT walk up to reuse an ancestor file:
|
|
62
|
+
* one project per folder, so binding a subproject never mutates the repo root.
|
|
63
|
+
*/
|
|
64
|
+
declare function bindProject(dir: string, projectId: string, projectName?: string): {
|
|
65
|
+
path: string;
|
|
66
|
+
created: boolean;
|
|
67
|
+
};
|
|
68
|
+
/** Alias kept for the MCP call sites. */
|
|
69
|
+
declare const bindProjectSync: typeof bindProject;
|
|
70
|
+
/**
|
|
71
|
+
* Non-destructively merge detected language/sdk/frameworks into the LOCAL
|
|
72
|
+
* `dir/.shipeasy` (create it if absent). Never touches `project_id` or any
|
|
73
|
+
* other field — this is what `shipeasy detect` calls per install target so the
|
|
74
|
+
* `docs` default-sdk works even before the project is bound.
|
|
75
|
+
*/
|
|
76
|
+
declare function mergeDetected(dir: string, detected: {
|
|
77
|
+
language?: string;
|
|
78
|
+
sdk?: string;
|
|
79
|
+
frameworks?: string[];
|
|
80
|
+
}): {
|
|
81
|
+
path: string;
|
|
82
|
+
created: boolean;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export { FILENAME, type ProjectConfig, SCHEMA_VERSION, bindProject, bindProjectSync, findProjectConfigDir, getBoundProjectId, getBoundProjectIdSync, getBoundSdk, getI18nClientKey, mergeDetected, readProjectConfig, readProjectConfigSync, saveI18nClientKey, writeProjectConfig };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// src/node-context/project-config.ts
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { readFile } from "fs/promises";
|
|
4
|
+
import { dirname, join, resolve } from "path";
|
|
5
|
+
var FILENAME = ".shipeasy";
|
|
6
|
+
var SCHEMA_VERSION = 1;
|
|
7
|
+
function configPath(dir) {
|
|
8
|
+
return join(dir, FILENAME);
|
|
9
|
+
}
|
|
10
|
+
function findProjectConfigDir(dir) {
|
|
11
|
+
let current = resolve(dir);
|
|
12
|
+
while (true) {
|
|
13
|
+
if (existsSync(join(current, FILENAME))) return current;
|
|
14
|
+
const parent = dirname(current);
|
|
15
|
+
if (parent === current) return null;
|
|
16
|
+
current = parent;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function parse(raw) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
} catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function readProjectConfigSync(dir) {
|
|
27
|
+
const found = findProjectConfigDir(dir);
|
|
28
|
+
if (!found) return {};
|
|
29
|
+
try {
|
|
30
|
+
return parse(readFileSync(join(found, FILENAME), "utf8"));
|
|
31
|
+
} catch {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function readProjectConfig(dir) {
|
|
36
|
+
const found = findProjectConfigDir(dir);
|
|
37
|
+
if (!found) return {};
|
|
38
|
+
try {
|
|
39
|
+
return parse(await readFile(join(found, FILENAME), "utf8"));
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function writeProjectConfig(dir, cfg) {
|
|
45
|
+
const withVersion = { schema_version: SCHEMA_VERSION, ...cfg };
|
|
46
|
+
writeFileSync(configPath(dir), JSON.stringify(withVersion, null, 2) + "\n", "utf8");
|
|
47
|
+
}
|
|
48
|
+
function readLocalConfig(dir) {
|
|
49
|
+
const p = configPath(dir);
|
|
50
|
+
if (!existsSync(p)) return {};
|
|
51
|
+
try {
|
|
52
|
+
return parse(readFileSync(p, "utf8"));
|
|
53
|
+
} catch {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function getBoundProjectId(dir) {
|
|
58
|
+
return readProjectConfigSync(dir).project_id;
|
|
59
|
+
}
|
|
60
|
+
var getBoundProjectIdSync = getBoundProjectId;
|
|
61
|
+
function getBoundSdk(dir) {
|
|
62
|
+
return readProjectConfigSync(dir).sdk;
|
|
63
|
+
}
|
|
64
|
+
function getI18nClientKey(dir) {
|
|
65
|
+
return readProjectConfigSync(dir).i18n?.client_key;
|
|
66
|
+
}
|
|
67
|
+
function saveI18nClientKey(dir, key) {
|
|
68
|
+
const target = findProjectConfigDir(dir) ?? dir;
|
|
69
|
+
const cfg = readLocalConfig(target);
|
|
70
|
+
writeProjectConfig(target, { ...cfg, i18n: { ...cfg.i18n, client_key: key } });
|
|
71
|
+
}
|
|
72
|
+
function bindProject(dir, projectId, projectName) {
|
|
73
|
+
const existed = existsSync(configPath(dir));
|
|
74
|
+
const cfg = readLocalConfig(dir);
|
|
75
|
+
writeProjectConfig(dir, {
|
|
76
|
+
...cfg,
|
|
77
|
+
project_id: projectId,
|
|
78
|
+
...projectName ? { project_name: projectName } : {}
|
|
79
|
+
});
|
|
80
|
+
return { path: configPath(dir), created: !existed };
|
|
81
|
+
}
|
|
82
|
+
var bindProjectSync = bindProject;
|
|
83
|
+
function mergeDetected(dir, detected) {
|
|
84
|
+
const existed = existsSync(configPath(dir));
|
|
85
|
+
const cfg = readLocalConfig(dir);
|
|
86
|
+
writeProjectConfig(dir, {
|
|
87
|
+
...cfg,
|
|
88
|
+
...detected.language ? { language: detected.language } : {},
|
|
89
|
+
...detected.sdk ? { sdk: detected.sdk } : {},
|
|
90
|
+
...detected.frameworks ? { frameworks: detected.frameworks } : {}
|
|
91
|
+
});
|
|
92
|
+
return { path: configPath(dir), created: !existed };
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
FILENAME,
|
|
96
|
+
SCHEMA_VERSION,
|
|
97
|
+
bindProject,
|
|
98
|
+
bindProjectSync,
|
|
99
|
+
findProjectConfigDir,
|
|
100
|
+
getBoundProjectId,
|
|
101
|
+
getBoundProjectIdSync,
|
|
102
|
+
getBoundSdk,
|
|
103
|
+
getI18nClientKey,
|
|
104
|
+
mergeDetected,
|
|
105
|
+
readProjectConfig,
|
|
106
|
+
readProjectConfigSync,
|
|
107
|
+
saveI18nClientKey,
|
|
108
|
+
writeProjectConfig
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=node-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node-context/project-config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\n\n/**\n * The shared `.shipeasy` project-binding file — the single source of truth for\n * both `@shipeasy/cli` and `@shipeasy/mcp`. Lives under `./node-context` because\n * it touches `node:fs`: **never import it from the worker-bundled graph** (the\n * openapi root/`./custom` entries stay fs-free and worker-safe).\n *\n * Model: **one `.shipeasy` per project**. The folder that holds the file IS the\n * project root. Reads walk up from the cwd to the *nearest* file (like git and\n * `.git`), so a subproject's own `.shipeasy` shadows any ancestor and the walk\n * stops there. Writes (`bindProject`, `mergeDetected`) target the LOCAL dir they\n * are given — they never reach up to reuse an ancestor — so each onboarded\n * subproject gets its own file.\n */\nexport const FILENAME = \".shipeasy\";\n\n/** Current `.shipeasy` schema version. Absent in a file → treat as legacy (1). */\nexport const SCHEMA_VERSION = 1;\n\nexport interface ProjectConfig {\n /** Shape version; lets future changes stay backward-compatible. */\n schema_version?: number;\n /**\n * The Shipeasy project this directory pushes to. Required for any mutating\n * CLI/MCP operation. Stops a session logged into project A from silently\n * writing to project B because someone's cwd is wrong.\n */\n project_id?: string;\n /** Human-readable project name — display only, not authoritative. */\n project_name?: string;\n /** Detected language (e.g. `typescript`, `python`) — written by `detect`. */\n language?: string;\n /** Docs SDK handle — the value `shipeasy docs` defaults to when `--sdk` is omitted. */\n sdk?: string;\n /** Detected frameworks (e.g. `[\"nextjs\", \"react\"]`) — written by `detect`. */\n frameworks?: string[];\n i18n?: {\n client_key?: string;\n };\n}\n\nfunction configPath(dir: string): string {\n return join(dir, FILENAME);\n}\n\n/**\n * Walk up from `dir` until a `.shipeasy` file is found, like git does for\n * `.git`. Returns the directory containing the file, or `null` if the search\n * hits the filesystem root without finding one.\n */\nexport function findProjectConfigDir(dir: string): string | null {\n let current = resolve(dir);\n while (true) {\n if (existsSync(join(current, FILENAME))) return current;\n const parent = dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n}\n\nfunction parse(raw: string): ProjectConfig {\n try {\n return JSON.parse(raw) as ProjectConfig;\n } catch {\n return {};\n }\n}\n\n/** Sync read of the nearest `.shipeasy` (walking up). Empty object if none. */\nexport function readProjectConfigSync(dir: string): ProjectConfig {\n const found = findProjectConfigDir(dir);\n if (!found) return {};\n try {\n return parse(readFileSync(join(found, FILENAME), \"utf8\"));\n } catch {\n return {};\n }\n}\n\n/** Async read of the nearest `.shipeasy` (walking up). Empty object if none. */\nexport async function readProjectConfig(dir: string): Promise<ProjectConfig> {\n const found = findProjectConfigDir(dir);\n if (!found) return {};\n try {\n return parse(await readFile(join(found, FILENAME), \"utf8\"));\n } catch {\n return {};\n }\n}\n\n/** Write `cfg` to `dir/.shipeasy` verbatim (stamping schema_version if absent). */\nexport function writeProjectConfig(dir: string, cfg: ProjectConfig): void {\n const withVersion: ProjectConfig = { schema_version: SCHEMA_VERSION, ...cfg };\n writeFileSync(configPath(dir), JSON.stringify(withVersion, null, 2) + \"\\n\", \"utf8\");\n}\n\n/** Read the LOCAL `.shipeasy` at exactly `dir` (no walk-up). Empty if absent. */\nfunction readLocalConfig(dir: string): ProjectConfig {\n const p = configPath(dir);\n if (!existsSync(p)) return {};\n try {\n return parse(readFileSync(p, \"utf8\"));\n } catch {\n return {};\n }\n}\n\n/** Project id from the nearest `.shipeasy` (sync). */\nexport function getBoundProjectId(dir: string): string | undefined {\n return readProjectConfigSync(dir).project_id;\n}\n/** Alias kept for the MCP call sites. */\nexport const getBoundProjectIdSync = getBoundProjectId;\n\n/** Docs SDK handle from the nearest `.shipeasy` (sync) — the `docs` default. */\nexport function getBoundSdk(dir: string): string | undefined {\n return readProjectConfigSync(dir).sdk;\n}\n\nexport function getI18nClientKey(dir: string): string | undefined {\n return readProjectConfigSync(dir).i18n?.client_key;\n}\n\nexport function saveI18nClientKey(dir: string, key: string): void {\n // Write into the nearest bound dir if one exists; otherwise create in `dir`.\n const target = findProjectConfigDir(dir) ?? dir;\n const cfg = readLocalConfig(target);\n writeProjectConfig(target, { ...cfg, i18n: { ...cfg.i18n, client_key: key } });\n}\n\n/**\n * Bind the LOCAL directory `dir` to `projectId` — writes `dir/.shipeasy`\n * directly (create, or merge over an existing LOCAL file to preserve `sdk` /\n * `i18n` / detection fields). It does NOT walk up to reuse an ancestor file:\n * one project per folder, so binding a subproject never mutates the repo root.\n */\nexport function bindProject(\n dir: string,\n projectId: string,\n projectName?: string,\n): { path: string; created: boolean } {\n const existed = existsSync(configPath(dir));\n const cfg = readLocalConfig(dir);\n writeProjectConfig(dir, {\n ...cfg,\n project_id: projectId,\n ...(projectName ? { project_name: projectName } : {}),\n });\n return { path: configPath(dir), created: !existed };\n}\n/** Alias kept for the MCP call sites. */\nexport const bindProjectSync = bindProject;\n\n/**\n * Non-destructively merge detected language/sdk/frameworks into the LOCAL\n * `dir/.shipeasy` (create it if absent). Never touches `project_id` or any\n * other field — this is what `shipeasy detect` calls per install target so the\n * `docs` default-sdk works even before the project is bound.\n */\nexport function mergeDetected(\n dir: string,\n detected: { language?: string; sdk?: string; frameworks?: string[] },\n): { path: string; created: boolean } {\n const existed = existsSync(configPath(dir));\n const cfg = readLocalConfig(dir);\n writeProjectConfig(dir, {\n ...cfg,\n ...(detected.language ? { language: detected.language } : {}),\n ...(detected.sdk ? { sdk: detected.sdk } : {}),\n ...(detected.frameworks ? { frameworks: detected.frameworks } : {}),\n });\n return { path: configPath(dir), created: !existed };\n}\n"],"mappings":"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","names":[]}
|