@zitadel/cli 0.0.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/LICENSE +21 -0
- package/README.md +345 -0
- package/SKILLS.md +71 -0
- package/bin/run.js +4 -0
- package/dist/commands/apply.mjs +62 -0
- package/dist/commands/apply.mjs.map +1 -0
- package/dist/commands/doctor.mjs +302 -0
- package/dist/commands/doctor.mjs.map +1 -0
- package/dist/commands/eject.mjs +137 -0
- package/dist/commands/eject.mjs.map +1 -0
- package/dist/commands/plan.mjs +52 -0
- package/dist/commands/plan.mjs.map +1 -0
- package/dist/commands/setup.mjs +694 -0
- package/dist/commands/setup.mjs.map +1 -0
- package/dist/commands/status.mjs +47 -0
- package/dist/commands/status.mjs.map +1 -0
- package/dist/orca-COsUnVoz.mjs +1006 -0
- package/dist/orca-COsUnVoz.mjs.map +1 -0
- package/dist/project-C3pSfbao.mjs +588 -0
- package/dist/project-C3pSfbao.mjs.map +1 -0
- package/dist/sync-Cuyh-X1J.mjs +733 -0
- package/dist/sync-Cuyh-X1J.mjs.map +1 -0
- package/oclif.manifest.json +521 -0
- package/package.json +113 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { a as readZitadelConfig, i as readRendererId, o as readZitadelSecret, p as ZitadelError, r as readDevelopmentIssuer, s as BaseCommand, u as isObject } from "../project-C3pSfbao.mjs";
|
|
2
|
+
import { r as issuerFromPort, t as createOrca } from "../orca-COsUnVoz.mjs";
|
|
3
|
+
import { Flags } from "@oclif/core";
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
import { chmod, readFile, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import "@zitadel/api/generated/endpoints/zitadelNextGen.zod";
|
|
8
|
+
//#region src/commands/doctor/checks/types.ts
|
|
9
|
+
/**
|
|
10
|
+
* Base class for checks: subclasses declare `name`, `path`, and a success
|
|
11
|
+
* `summary`, and implement the single {@link verify} method that throws on
|
|
12
|
+
* failure. {@link run} wraps it so a thrown error becomes a `fail` outcome
|
|
13
|
+
* carrying the error message, and success becomes a `pass` with `summary`.
|
|
14
|
+
*
|
|
15
|
+
* {@link fix} defaults to a no-op: checks whose failure has no safe automatic
|
|
16
|
+
* remedy (a missing secret, an invalid user schema) simply do not override it.
|
|
17
|
+
*/
|
|
18
|
+
var AbstractSanityCheck = class {
|
|
19
|
+
async run(ctx) {
|
|
20
|
+
try {
|
|
21
|
+
await this.verify(ctx);
|
|
22
|
+
return {
|
|
23
|
+
name: this.name,
|
|
24
|
+
status: "pass",
|
|
25
|
+
message: this.summary,
|
|
26
|
+
path: this.path
|
|
27
|
+
};
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return {
|
|
30
|
+
name: this.name,
|
|
31
|
+
status: "fail",
|
|
32
|
+
message: error instanceof Error ? error.message : String(error),
|
|
33
|
+
path: this.path
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async fix(_ctx) {}
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/commands/doctor/checks/config.ts
|
|
41
|
+
/** Verifies `zitadel.json` exists and parses. */
|
|
42
|
+
var ConfigCheck = class extends AbstractSanityCheck {
|
|
43
|
+
name = "config";
|
|
44
|
+
path = "zitadel.json";
|
|
45
|
+
summary = "zitadel.json parses";
|
|
46
|
+
async verify(ctx) {
|
|
47
|
+
await readZitadelConfig(ctx.cwd);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/commands/doctor/checks/secret.ts
|
|
52
|
+
/** Verifies `.zitadel/secret` exists and parses. */
|
|
53
|
+
var SecretCheck = class extends AbstractSanityCheck {
|
|
54
|
+
name = "secret";
|
|
55
|
+
path = ".zitadel/secret";
|
|
56
|
+
summary = ".zitadel/secret parses";
|
|
57
|
+
async verify(ctx) {
|
|
58
|
+
await readZitadelSecret(ctx.cwd);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/commands/doctor/checks/secret-permissions.ts
|
|
63
|
+
/** Verifies `.zitadel/secret` is locked down to `0600`, and re-locks it on fix. */
|
|
64
|
+
var SecretPermissionsCheck = class extends AbstractSanityCheck {
|
|
65
|
+
name = "secret-permissions";
|
|
66
|
+
path = ".zitadel/secret";
|
|
67
|
+
summary = ".zitadel/secret has 0600 permissions";
|
|
68
|
+
async verify(ctx) {
|
|
69
|
+
const mode = (await stat(join(ctx.cwd, ".zitadel/secret"))).mode & 511;
|
|
70
|
+
if (mode !== 384) throw new Error(`expected 0600, got ${mode.toString(8)}`);
|
|
71
|
+
}
|
|
72
|
+
async fix(ctx) {
|
|
73
|
+
if (ctx.dryRun) return;
|
|
74
|
+
await chmod(join(ctx.cwd, ".zitadel/secret"), 384);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/commands/doctor/checks/gitignore.ts
|
|
79
|
+
/** The entries `.gitignore` must carry to keep secrets and env files untracked. */
|
|
80
|
+
const REQUIRED_ENTRIES = [
|
|
81
|
+
".zitadel/secret",
|
|
82
|
+
".env*",
|
|
83
|
+
"!.env.example"
|
|
84
|
+
];
|
|
85
|
+
/** Verifies `.gitignore` excludes the local secret and env files; appends any missing. */
|
|
86
|
+
var GitignoreCheck = class extends AbstractSanityCheck {
|
|
87
|
+
name = "gitignore";
|
|
88
|
+
path = ".gitignore";
|
|
89
|
+
summary = ".gitignore protects local secret/env files";
|
|
90
|
+
async verify(ctx) {
|
|
91
|
+
const lines = (await readFile(join(ctx.cwd, ".gitignore"), "utf8")).split(/\r?\n/g);
|
|
92
|
+
for (const entry of REQUIRED_ENTRIES) if (!lines.includes(entry)) throw new Error(`missing ${entry}`);
|
|
93
|
+
}
|
|
94
|
+
async fix(ctx) {
|
|
95
|
+
const path = join(ctx.cwd, ".gitignore");
|
|
96
|
+
const existing = await readFile(path, "utf8").catch(() => "");
|
|
97
|
+
const lines = existing.split(/\r?\n/g);
|
|
98
|
+
const missing = REQUIRED_ENTRIES.filter((entry) => !lines.includes(entry));
|
|
99
|
+
if (missing.length === 0 || ctx.dryRun) return;
|
|
100
|
+
await writeFile(path, `${existing}${existing.length > 0 && !existing.endsWith("\n") ? "\n" : ""}${missing.join("\n")}\n`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/commands/doctor/checks/env-example.ts
|
|
105
|
+
/** The keys `.env.example` must document for a Zitadel-managed project. */
|
|
106
|
+
const REQUIRED_KEYS = [
|
|
107
|
+
"ZITADEL_PROJECT_ID",
|
|
108
|
+
"ZITADEL_ENVIRONMENT",
|
|
109
|
+
"ZITADEL_ISSUER"
|
|
110
|
+
];
|
|
111
|
+
/** Verifies `.env.example` documents the required Zitadel keys; appends any missing. */
|
|
112
|
+
var EnvExampleCheck = class extends AbstractSanityCheck {
|
|
113
|
+
name = "env-example";
|
|
114
|
+
path = ".env.example";
|
|
115
|
+
summary = ".env.example references required keys";
|
|
116
|
+
async verify(ctx) {
|
|
117
|
+
const contents = await readFile(join(ctx.cwd, ".env.example"), "utf8");
|
|
118
|
+
for (const key of REQUIRED_KEYS) if (!contents.includes(`${key}=`)) throw new Error(`missing ${key}`);
|
|
119
|
+
}
|
|
120
|
+
async fix(ctx) {
|
|
121
|
+
const path = join(ctx.cwd, ".env.example");
|
|
122
|
+
const existing = await readFile(path, "utf8").catch(() => "");
|
|
123
|
+
const missing = REQUIRED_KEYS.filter((key) => !existing.includes(`${key}=`));
|
|
124
|
+
if (missing.length === 0 || ctx.dryRun) return;
|
|
125
|
+
await writeFile(path, `${existing}${existing.length > 0 && !existing.endsWith("\n") ? "\n" : ""}${missing.map((key) => `${key}=`).join("\n")}\n`);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/commands/doctor/checks/framework.ts
|
|
130
|
+
/** Verifies the framework detected on disk matches the one recorded in config. */
|
|
131
|
+
var FrameworkCheck = class extends AbstractSanityCheck {
|
|
132
|
+
name = "framework";
|
|
133
|
+
path = "zitadel.json";
|
|
134
|
+
summary = "Detected framework matches recorded framework";
|
|
135
|
+
async verify(ctx) {
|
|
136
|
+
const config = await readZitadelConfig(ctx.cwd);
|
|
137
|
+
const detected = await ctx.orca.detect(ctx.cwd);
|
|
138
|
+
const recorded = isObject(config.framework) ? config.framework.id : void 0;
|
|
139
|
+
if (recorded !== detected.id) throw new Error(`expected ${String(recorded)}, detected ${detected.id}`);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/commands/doctor/patch-context.ts
|
|
144
|
+
/**
|
|
145
|
+
* Reconstructs a {@link PatchContext} from the on-disk project (config, secret)
|
|
146
|
+
* plus fresh framework detection, so a patcher repair can rebuild its plan.
|
|
147
|
+
* Used by the dependency check's `fix`, which reclaims the framework-specific
|
|
148
|
+
* SDK package via `patcher.repair`. The user schema and flow definition are
|
|
149
|
+
* server-owned and no longer scaffolded locally, so nothing here reads them.
|
|
150
|
+
*/
|
|
151
|
+
async function loadPatchContext(cwd, orca) {
|
|
152
|
+
const config = await readZitadelConfig(cwd);
|
|
153
|
+
const secret = await readZitadelSecret(cwd);
|
|
154
|
+
const framework = await orca.detect(cwd);
|
|
155
|
+
return {
|
|
156
|
+
framework,
|
|
157
|
+
rendererId: readRendererId(config),
|
|
158
|
+
issuer: await resolveIssuer(cwd, config, framework),
|
|
159
|
+
server: typeof config.server === "string" ? config.server : "",
|
|
160
|
+
project: {
|
|
161
|
+
id: secret.project_id,
|
|
162
|
+
projectSecret: secret.project_secret,
|
|
163
|
+
previewSecret: secret.preview_secret,
|
|
164
|
+
previewOrigins: secret.preview_origins,
|
|
165
|
+
createdAt: secret.created_at
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async function resolveIssuer(cwd, config, facts) {
|
|
170
|
+
const fromConfig = readDevelopmentIssuer(config);
|
|
171
|
+
if (fromConfig && fromConfig.length > 0) return fromConfig;
|
|
172
|
+
const state = await readState(cwd);
|
|
173
|
+
if (typeof state?.dev_port === "number") return issuerFromPort(state.dev_port);
|
|
174
|
+
return facts.url;
|
|
175
|
+
}
|
|
176
|
+
async function readState(cwd) {
|
|
177
|
+
try {
|
|
178
|
+
const contents = await readFile(join(cwd, ".zitadel/state.json"), "utf8");
|
|
179
|
+
return JSON.parse(contents);
|
|
180
|
+
} catch {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/commands/doctor/checks/dependency.ts
|
|
186
|
+
/**
|
|
187
|
+
* Verifies the project still declares a Zitadel SDK dependency in
|
|
188
|
+
* `package.json`. The patcher adds a scoped package (e.g.
|
|
189
|
+
* `@zitadel/sdk-next`); the check is generic over the `@zitadel*`
|
|
190
|
+
* scope so any framework renderer's dependency satisfies it.
|
|
191
|
+
*/
|
|
192
|
+
var DependencyCheck = class extends AbstractSanityCheck {
|
|
193
|
+
name = "dependency";
|
|
194
|
+
path = "package.json";
|
|
195
|
+
summary = "package.json depends on a Zitadel SDK package";
|
|
196
|
+
async verify(ctx) {
|
|
197
|
+
const pkg = JSON.parse(await readFile(join(ctx.cwd, "package.json"), "utf8"));
|
|
198
|
+
if (![...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})].some((name) => name.startsWith("@zitadel"))) throw new Error("no @zitadel* dependency found in package.json");
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Repairs by reclaiming the patcher's managed artifacts: rebuilds the
|
|
202
|
+
* `PatchContext` from disk and calls `patcher.repair`, which re-adds the
|
|
203
|
+
* SDK dependency via its `add-dep` op. The exact package name is framework
|
|
204
|
+
* + renderer specific and known only to the patcher (which deliberately
|
|
205
|
+
* hides its file-op plan behind the family-neutral `Patcher` interface),
|
|
206
|
+
* so going through `repair` is the only sanctioned path.
|
|
207
|
+
*/
|
|
208
|
+
async fix(ctx) {
|
|
209
|
+
const patchCtx = await loadPatchContext(ctx.cwd, ctx.orca);
|
|
210
|
+
await ctx.orca.patcherFor(patchCtx.framework.id).repair(patchCtx, {
|
|
211
|
+
cwd: ctx.cwd,
|
|
212
|
+
dryRun: ctx.dryRun,
|
|
213
|
+
force: true
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/commands/doctor/checks/project-match.ts
|
|
219
|
+
/** Verifies `.zitadel/secret`'s project_id matches `zitadel.json`'s project. */
|
|
220
|
+
var ProjectMatchCheck = class extends AbstractSanityCheck {
|
|
221
|
+
name = "project-match";
|
|
222
|
+
path = ".zitadel/secret";
|
|
223
|
+
summary = ".zitadel/secret project_id matches zitadel.json project";
|
|
224
|
+
async verify(ctx) {
|
|
225
|
+
const config = await readZitadelConfig(ctx.cwd);
|
|
226
|
+
const secret = await readZitadelSecret(ctx.cwd);
|
|
227
|
+
const configProject = typeof config.project === "string" ? config.project : void 0;
|
|
228
|
+
if (secret.project_id !== configProject) throw new Error(".zitadel/secret project_id does not match zitadel.json project");
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/commands/doctor/checks/index.ts
|
|
233
|
+
/** Every diagnostic the `doctor` command runs, in display order. */
|
|
234
|
+
const SANITY_CHECKS = [
|
|
235
|
+
new ConfigCheck(),
|
|
236
|
+
new SecretCheck(),
|
|
237
|
+
new SecretPermissionsCheck(),
|
|
238
|
+
new GitignoreCheck(),
|
|
239
|
+
new EnvExampleCheck(),
|
|
240
|
+
new FrameworkCheck(),
|
|
241
|
+
new DependencyCheck(),
|
|
242
|
+
new ProjectMatchCheck()
|
|
243
|
+
];
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/commands/doctor/index.ts
|
|
246
|
+
/**
|
|
247
|
+
* `zitadel doctor` — verify generated files and local state.
|
|
248
|
+
*
|
|
249
|
+
* Runs every registered {@link SANITY_CHECKS} entry and emits the aggregate
|
|
250
|
+
* result; if any check fails it throws `E_VALIDATION` carrying the full check
|
|
251
|
+
* details. With `--fix`, each failing check first attempts its own repair (a
|
|
252
|
+
* no-op for checks with no safe automatic remedy), then the battery re-runs.
|
|
253
|
+
*
|
|
254
|
+
* The `--fix` loop is best-effort: a repair that throws (e.g. a missing
|
|
255
|
+
* prerequisite file the check itself would also flag) is logged at debug
|
|
256
|
+
* level and skipped, not propagated — the post-fix re-verify still reports
|
|
257
|
+
* whatever remains broken.
|
|
258
|
+
*/
|
|
259
|
+
var Doctor = class Doctor extends BaseCommand {
|
|
260
|
+
static description = "Verify generated files and local state.";
|
|
261
|
+
static flags = { fix: Flags.boolean({ description: "Re-apply missing managed files." }) };
|
|
262
|
+
async run() {
|
|
263
|
+
const { flags } = await this.parse(Doctor);
|
|
264
|
+
await this.toMeta(flags);
|
|
265
|
+
const { cwd, dryRun } = this.meta;
|
|
266
|
+
const ctx = {
|
|
267
|
+
cwd,
|
|
268
|
+
orca: createOrca(),
|
|
269
|
+
dryRun
|
|
270
|
+
};
|
|
271
|
+
if (flags.fix) {
|
|
272
|
+
const before = await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)));
|
|
273
|
+
for (const [index, check] of SANITY_CHECKS.entries()) {
|
|
274
|
+
if (before[index]?.status !== "fail") continue;
|
|
275
|
+
try {
|
|
276
|
+
await check.fix(ctx);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
consola.debug(`doctor --fix: ${check.name} repair failed`, error);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const checks = await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)));
|
|
283
|
+
const failed = checks.filter((check) => check.status === "fail");
|
|
284
|
+
const data = {
|
|
285
|
+
title: failed.length === 0 ? "Zitadel doctor passed." : "Zitadel doctor found issues.",
|
|
286
|
+
ok: failed.length === 0,
|
|
287
|
+
checks
|
|
288
|
+
};
|
|
289
|
+
if (failed.length > 0) throw new ZitadelError("E_VALIDATION", "Zitadel doctor found issues", {
|
|
290
|
+
hint: "Run `npx @zitadel/cli@latest doctor --fix` to re-apply missing managed files.",
|
|
291
|
+
details: data
|
|
292
|
+
});
|
|
293
|
+
return this.emit({
|
|
294
|
+
status: "ok",
|
|
295
|
+
data
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
//#endregion
|
|
300
|
+
export { Doctor as default };
|
|
301
|
+
|
|
302
|
+
//# sourceMappingURL=doctor.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.mjs","names":[],"sources":["../../src/commands/doctor/checks/types.ts","../../src/commands/doctor/checks/config.ts","../../src/commands/doctor/checks/secret.ts","../../src/commands/doctor/checks/secret-permissions.ts","../../src/commands/doctor/checks/gitignore.ts","../../src/commands/doctor/checks/env-example.ts","../../src/commands/doctor/checks/framework.ts","../../src/commands/doctor/patch-context.ts","../../src/commands/doctor/checks/dependency.ts","../../src/commands/doctor/checks/project-match.ts","../../src/commands/doctor/checks/index.ts","../../src/commands/doctor/index.ts"],"sourcesContent":["import type { Orca } from \"../../../lib/orca\";\n\n/** Pass/fail outcome of a single {@link SanityCheck}. */\nexport type CheckOutcome = {\n name: string;\n status: \"pass\" | \"fail\";\n message: string;\n path?: string;\n};\n\n/** Everything a check needs to inspect or repair a project. */\nexport type CheckContext = {\n readonly cwd: string;\n readonly orca: Orca;\n /** When true, {@link SanityCheck.fix} must preview without writing. */\n readonly dryRun: boolean;\n};\n\n/**\n * One diagnostic the `doctor` command runs. Each concrete check is a small\n * standalone class that both verifies its concern ({@link run}) and knows how\n * to repair it ({@link fix}); the command executes every registered check,\n * aggregates the {@link CheckOutcome}s, and (under `--fix`) repairs the ones\n * that failed.\n */\nexport interface SanityCheck {\n /** Stable identifier surfaced in logs and the JSON envelope. */\n readonly name: string;\n run(ctx: CheckContext): Promise<CheckOutcome>;\n /** Repair what this check verifies. A no-op when there is no safe auto-fix. */\n fix(ctx: CheckContext): Promise<void>;\n}\n\n/**\n * Base class for checks: subclasses declare `name`, `path`, and a success\n * `summary`, and implement the single {@link verify} method that throws on\n * failure. {@link run} wraps it so a thrown error becomes a `fail` outcome\n * carrying the error message, and success becomes a `pass` with `summary`.\n *\n * {@link fix} defaults to a no-op: checks whose failure has no safe automatic\n * remedy (a missing secret, an invalid user schema) simply do not override it.\n */\nexport abstract class AbstractSanityCheck implements SanityCheck {\n abstract readonly name: string;\n abstract readonly path: string;\n protected abstract readonly summary: string;\n\n /** Throw to signal failure; the thrown message is surfaced to the user. */\n protected abstract verify(ctx: CheckContext): Promise<void>;\n\n async run(ctx: CheckContext): Promise<CheckOutcome> {\n try {\n await this.verify(ctx);\n return { name: this.name, status: \"pass\", message: this.summary, path: this.path };\n } catch (error) {\n return {\n name: this.name,\n status: \"fail\",\n message: error instanceof Error ? error.message : String(error),\n path: this.path,\n };\n }\n }\n\n async fix(_ctx: CheckContext): Promise<void> {\n return;\n }\n}\n","import { readZitadelConfig } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `zitadel.json` exists and parses. */\nexport class ConfigCheck extends AbstractSanityCheck {\n readonly name = \"config\";\n readonly path = \"zitadel.json\";\n protected readonly summary = \"zitadel.json parses\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n await readZitadelConfig(ctx.cwd);\n }\n}\n","import { readZitadelSecret } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `.zitadel/secret` exists and parses. */\nexport class SecretCheck extends AbstractSanityCheck {\n readonly name = \"secret\";\n readonly path = \".zitadel/secret\";\n protected readonly summary = \".zitadel/secret parses\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n await readZitadelSecret(ctx.cwd);\n }\n}\n","import { chmod, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `.zitadel/secret` is locked down to `0600`, and re-locks it on fix. */\nexport class SecretPermissionsCheck extends AbstractSanityCheck {\n readonly name = \"secret-permissions\";\n readonly path = \".zitadel/secret\";\n protected readonly summary = \".zitadel/secret has 0600 permissions\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const mode = (await stat(join(ctx.cwd, \".zitadel/secret\"))).mode & 0o777;\n if (mode !== 0o600) {\n throw new Error(`expected 0600, got ${mode.toString(8)}`);\n }\n }\n\n override async fix(ctx: CheckContext): Promise<void> {\n if (ctx.dryRun) {\n return;\n }\n await chmod(join(ctx.cwd, \".zitadel/secret\"), 0o600);\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** The entries `.gitignore` must carry to keep secrets and env files untracked. */\nconst REQUIRED_ENTRIES = [\".zitadel/secret\", \".env*\", \"!.env.example\"];\n\n/** Verifies `.gitignore` excludes the local secret and env files; appends any missing. */\nexport class GitignoreCheck extends AbstractSanityCheck {\n readonly name = \"gitignore\";\n readonly path = \".gitignore\";\n protected readonly summary = \".gitignore protects local secret/env files\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const lines = (await readFile(join(ctx.cwd, \".gitignore\"), \"utf8\")).split(/\\r?\\n/g);\n for (const entry of REQUIRED_ENTRIES) {\n if (!lines.includes(entry)) {\n throw new Error(`missing ${entry}`);\n }\n }\n }\n\n override async fix(ctx: CheckContext): Promise<void> {\n const path = join(ctx.cwd, \".gitignore\");\n const existing = await readFile(path, \"utf8\").catch(() => \"\");\n const lines = existing.split(/\\r?\\n/g);\n const missing = REQUIRED_ENTRIES.filter((entry) => !lines.includes(entry));\n if (missing.length === 0 || ctx.dryRun) {\n return;\n }\n const prefix = existing.length > 0 && !existing.endsWith(\"\\n\") ? \"\\n\" : \"\";\n await writeFile(path, `${existing}${prefix}${missing.join(\"\\n\")}\\n`);\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** The keys `.env.example` must document for a Zitadel-managed project. */\nconst REQUIRED_KEYS = [\"ZITADEL_PROJECT_ID\", \"ZITADEL_ENVIRONMENT\", \"ZITADEL_ISSUER\"];\n\n/** Verifies `.env.example` documents the required Zitadel keys; appends any missing. */\nexport class EnvExampleCheck extends AbstractSanityCheck {\n readonly name = \"env-example\";\n readonly path = \".env.example\";\n protected readonly summary = \".env.example references required keys\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const contents = await readFile(join(ctx.cwd, \".env.example\"), \"utf8\");\n for (const key of REQUIRED_KEYS) {\n if (!contents.includes(`${key}=`)) {\n throw new Error(`missing ${key}`);\n }\n }\n }\n\n override async fix(ctx: CheckContext): Promise<void> {\n const path = join(ctx.cwd, \".env.example\");\n const existing = await readFile(path, \"utf8\").catch(() => \"\");\n const missing = REQUIRED_KEYS.filter((key) => !existing.includes(`${key}=`));\n if (missing.length === 0 || ctx.dryRun) {\n return;\n }\n const prefix = existing.length > 0 && !existing.endsWith(\"\\n\") ? \"\\n\" : \"\";\n await writeFile(path, `${existing}${prefix}${missing.map((key) => `${key}=`).join(\"\\n\")}\\n`);\n }\n}\n","import { isObject } from \"../../../lib/json\";\nimport { readZitadelConfig } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies the framework detected on disk matches the one recorded in config. */\nexport class FrameworkCheck extends AbstractSanityCheck {\n readonly name = \"framework\";\n readonly path = \"zitadel.json\";\n protected readonly summary = \"Detected framework matches recorded framework\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const config = await readZitadelConfig(ctx.cwd);\n const detected = await ctx.orca.detect(ctx.cwd);\n const recorded = isObject(config.framework) ? config.framework.id : undefined;\n if (recorded !== detected.id) {\n throw new Error(`expected ${String(recorded)}, detected ${detected.id}`);\n }\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { issuerFromPort, type FrameworkFacts, type Orca } from \"../../lib/orca\";\nimport type { PatchContext } from \"../../lib/orca/patchers/types\";\nimport { readDevelopmentIssuer, readRendererId, readZitadelConfig, readZitadelSecret } from \"../../lib/project\";\n\n/**\n * Reconstructs a {@link PatchContext} from the on-disk project (config, secret)\n * plus fresh framework detection, so a patcher repair can rebuild its plan.\n * Used by the dependency check's `fix`, which reclaims the framework-specific\n * SDK package via `patcher.repair`. The user schema and flow definition are\n * server-owned and no longer scaffolded locally, so nothing here reads them.\n */\nexport async function loadPatchContext(cwd: string, orca: Orca): Promise<PatchContext> {\n const config = await readZitadelConfig(cwd);\n const secret = await readZitadelSecret(cwd);\n const framework = await orca.detect(cwd);\n return {\n framework,\n rendererId: readRendererId(config),\n issuer: await resolveIssuer(cwd, config, framework),\n server: typeof config.server === \"string\" ? config.server : \"\",\n project: {\n id: secret.project_id,\n projectSecret: secret.project_secret,\n previewSecret: secret.preview_secret,\n previewOrigins: secret.preview_origins,\n createdAt: secret.created_at,\n },\n };\n}\n\nasync function resolveIssuer(\n cwd: string,\n config: Record<string, unknown>,\n facts: FrameworkFacts,\n): Promise<string> {\n const fromConfig = readDevelopmentIssuer(config);\n if (fromConfig && fromConfig.length > 0) {\n return fromConfig;\n }\n const state = await readState(cwd);\n if (typeof state?.dev_port === \"number\") {\n return issuerFromPort(state.dev_port);\n }\n return facts.url;\n}\n\nasync function readState(cwd: string): Promise<{ dev_port?: number } | undefined> {\n try {\n const contents = await readFile(join(cwd, \".zitadel/state.json\"), \"utf8\");\n return JSON.parse(contents) as { dev_port?: number };\n } catch {\n return undefined;\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { loadPatchContext } from \"../patch-context\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/**\n * Verifies the project still declares a Zitadel SDK dependency in\n * `package.json`. The patcher adds a scoped package (e.g.\n * `@zitadel/sdk-next`); the check is generic over the `@zitadel*`\n * scope so any framework renderer's dependency satisfies it.\n */\nexport class DependencyCheck extends AbstractSanityCheck {\n readonly name = \"dependency\";\n readonly path = \"package.json\";\n protected readonly summary = \"package.json depends on a Zitadel SDK package\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const pkg = JSON.parse(await readFile(join(ctx.cwd, \"package.json\"), \"utf8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const names = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ];\n if (!names.some((name) => name.startsWith(\"@zitadel\"))) {\n throw new Error(\"no @zitadel* dependency found in package.json\");\n }\n }\n\n /**\n * Repairs by reclaiming the patcher's managed artifacts: rebuilds the\n * `PatchContext` from disk and calls `patcher.repair`, which re-adds the\n * SDK dependency via its `add-dep` op. The exact package name is framework\n * + renderer specific and known only to the patcher (which deliberately\n * hides its file-op plan behind the family-neutral `Patcher` interface),\n * so going through `repair` is the only sanctioned path.\n */\n override async fix(ctx: CheckContext): Promise<void> {\n const patchCtx = await loadPatchContext(ctx.cwd, ctx.orca);\n await ctx.orca.patcherFor(patchCtx.framework.id).repair(patchCtx, {\n cwd: ctx.cwd,\n dryRun: ctx.dryRun,\n force: true,\n });\n }\n}\n","import { readZitadelConfig, readZitadelSecret } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `.zitadel/secret`'s project_id matches `zitadel.json`'s project. */\nexport class ProjectMatchCheck extends AbstractSanityCheck {\n readonly name = \"project-match\";\n readonly path = \".zitadel/secret\";\n protected readonly summary = \".zitadel/secret project_id matches zitadel.json project\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const config = await readZitadelConfig(ctx.cwd);\n const secret = await readZitadelSecret(ctx.cwd);\n const configProject = typeof config.project === \"string\" ? config.project : undefined;\n if (secret.project_id !== configProject) {\n throw new Error(\".zitadel/secret project_id does not match zitadel.json project\");\n }\n }\n}\n","/**\n * Public surface for the doctor sanity checks. The `doctor` command imports\n * {@link SANITY_CHECKS} and runs every entry, aggregating the outcomes. Each\n * check is a small standalone class (see its own file); add a new diagnostic\n * by writing a class and appending an instance to the registry below.\n */\nimport type { SanityCheck } from \"./types\";\nimport { ConfigCheck } from \"./config\";\nimport { SecretCheck } from \"./secret\";\nimport { SecretPermissionsCheck } from \"./secret-permissions\";\nimport { GitignoreCheck } from \"./gitignore\";\nimport { EnvExampleCheck } from \"./env-example\";\nimport { FrameworkCheck } from \"./framework\";\nimport { DependencyCheck } from \"./dependency\";\nimport { ProjectMatchCheck } from \"./project-match\";\n\nexport type { SanityCheck, CheckContext, CheckOutcome } from \"./types\";\nexport { AbstractSanityCheck } from \"./types\";\nexport { ConfigCheck } from \"./config\";\nexport { SecretCheck } from \"./secret\";\nexport { SecretPermissionsCheck } from \"./secret-permissions\";\nexport { GitignoreCheck } from \"./gitignore\";\nexport { EnvExampleCheck } from \"./env-example\";\nexport { FrameworkCheck } from \"./framework\";\nexport { SchemaCheck } from \"./schema\";\nexport { DependencyCheck } from \"./dependency\";\nexport { ProjectMatchCheck } from \"./project-match\";\n\n/** Every diagnostic the `doctor` command runs, in display order. */\nexport const SANITY_CHECKS: ReadonlyArray<SanityCheck> = [\n new ConfigCheck(),\n new SecretCheck(),\n new SecretPermissionsCheck(),\n new GitignoreCheck(),\n new EnvExampleCheck(),\n new FrameworkCheck(),\n // SchemaCheck is disabled: the user schema is now provisioned server-side\n // and no longer scaffolded into `.zitadel/schemas/user.json`, so there is\n // no local file to verify. The check is kept (imported/exported below) for\n // the future pull-based workflow that will re-introduce local resources.\n // new SchemaCheck(),\n new DependencyCheck(),\n new ProjectMatchCheck(),\n];\n","import { Flags } from \"@oclif/core\";\nimport consola from \"consola\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../../lib/oclif\";\nimport { ZitadelError } from \"../../lib/errors\";\nimport { createOrca } from \"../../lib/orca\";\nimport { SANITY_CHECKS, type CheckContext } from \"./checks\";\n\n/**\n * `zitadel doctor` — verify generated files and local state.\n *\n * Runs every registered {@link SANITY_CHECKS} entry and emits the aggregate\n * result; if any check fails it throws `E_VALIDATION` carrying the full check\n * details. With `--fix`, each failing check first attempts its own repair (a\n * no-op for checks with no safe automatic remedy), then the battery re-runs.\n *\n * The `--fix` loop is best-effort: a repair that throws (e.g. a missing\n * prerequisite file the check itself would also flag) is logged at debug\n * level and skipped, not propagated — the post-fix re-verify still reports\n * whatever remains broken.\n */\nexport default class Doctor extends BaseCommand {\n static override description = \"Verify generated files and local state.\";\n static override flags = {\n fix: Flags.boolean({ description: \"Re-apply missing managed files.\" }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Doctor);\n await this.toMeta(flags);\n const { cwd, dryRun } = this.meta;\n const ctx: CheckContext = { cwd, orca: createOrca(), dryRun };\n\n if (flags.fix) {\n const before = await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)));\n for (const [index, check] of SANITY_CHECKS.entries()) {\n if (before[index]?.status !== \"fail\") {\n continue;\n }\n try {\n await check.fix(ctx);\n } catch (error) {\n consola.debug(`doctor --fix: ${check.name} repair failed`, error);\n }\n }\n }\n\n const checks = await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)));\n const failed = checks.filter((check) => check.status === \"fail\");\n const data = {\n title: failed.length === 0 ? \"Zitadel doctor passed.\" : \"Zitadel doctor found issues.\",\n ok: failed.length === 0,\n checks,\n };\n\n if (failed.length > 0) {\n throw new ZitadelError(\"E_VALIDATION\", \"Zitadel doctor found issues\", {\n hint: \"Run `npx @zitadel/cli@latest doctor --fix` to re-apply missing managed files.\",\n details: data,\n });\n }\n\n return this.emit({ status: \"ok\", data });\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { a as readZitadelConfig, i as readRendererId, p as ZitadelError, s as BaseCommand } from "../project-C3pSfbao.mjs";
|
|
2
|
+
import { t as createOrca } from "../orca-COsUnVoz.mjs";
|
|
3
|
+
import { readFile, rename, rm, stat } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
//#region src/commands/eject.ts
|
|
6
|
+
/**
|
|
7
|
+
* Asks the framework patcher which artifacts it owns. Falls back to the
|
|
8
|
+
* framework-agnostic set (`zitadel.json`, `.zitadel/`, `.env.local`) when the
|
|
9
|
+
* framework or its patcher cannot be resolved, so an orphaned/partial project
|
|
10
|
+
* can still be cleaned up.
|
|
11
|
+
*/
|
|
12
|
+
async function resolveEjectActions(cwd) {
|
|
13
|
+
const fallback = {
|
|
14
|
+
markedFiles: [],
|
|
15
|
+
rootConfigFiles: ["zitadel.json"],
|
|
16
|
+
directories: [".zitadel"],
|
|
17
|
+
envBackups: [".env.local"],
|
|
18
|
+
dependencies: []
|
|
19
|
+
};
|
|
20
|
+
const orca = createOrca();
|
|
21
|
+
const framework = await orca.tryDetect(cwd);
|
|
22
|
+
if (!framework) return fallback;
|
|
23
|
+
try {
|
|
24
|
+
const config = await readZitadelConfig(cwd).catch(() => ({}));
|
|
25
|
+
return orca.patcherFor(framework.id).artifacts({
|
|
26
|
+
framework,
|
|
27
|
+
rendererId: readRendererId(config)
|
|
28
|
+
});
|
|
29
|
+
} catch {
|
|
30
|
+
return fallback;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Builds the `next_commands` envelope field — manual follow-ups `eject` can't
|
|
35
|
+
* safely run itself: deleting the `.env.local.ejected-*` backups it created
|
|
36
|
+
* (only when some were made), and uninstalling the SDK packages the patcher
|
|
37
|
+
* added (the CLI never modifies the user's `package.json` + lockfile +
|
|
38
|
+
* `node_modules` directly; it just suggests the command).
|
|
39
|
+
*/
|
|
40
|
+
function assembleNextCommands(backedUp, dependencies) {
|
|
41
|
+
const commands = [];
|
|
42
|
+
if (backedUp.length > 0) commands.push("rm -f .env.local.ejected-*");
|
|
43
|
+
for (const dep of dependencies) commands.push(`npm uninstall ${dep}`);
|
|
44
|
+
return commands;
|
|
45
|
+
}
|
|
46
|
+
async function pathExists(path) {
|
|
47
|
+
try {
|
|
48
|
+
await stat(path);
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `zitadel eject` — remove managed files and local Zitadel state.
|
|
56
|
+
*
|
|
57
|
+
* Removes Zitadel-managed files from the project, leaving the remote project
|
|
58
|
+
* untouched. The set of files comes from the framework patcher's
|
|
59
|
+
* {@link import("../lib/orca/patchers/types").Patcher.artifacts}, so the patcher
|
|
60
|
+
* is the single source of truth for what its integration owns.
|
|
61
|
+
*
|
|
62
|
+
* Marked code files are removed only when they still carry the managed marker
|
|
63
|
+
* (user-replaced files are preserved); `zitadel.json` is removed; `.env.local`
|
|
64
|
+
* is renamed to a timestamped backup; and `.zitadel/` is removed wholesale.
|
|
65
|
+
* `--dry-run` reports without touching the filesystem; non-interactive runs
|
|
66
|
+
* require `--force`.
|
|
67
|
+
*/
|
|
68
|
+
var Eject = class Eject extends BaseCommand {
|
|
69
|
+
static description = "Remove managed files and local Zitadel state.";
|
|
70
|
+
static aliases = ["uninstall"];
|
|
71
|
+
async run() {
|
|
72
|
+
const { flags } = await this.parse(Eject);
|
|
73
|
+
await this.toMeta(flags);
|
|
74
|
+
const { cwd, force, nonInteractive, dryRun } = this.meta;
|
|
75
|
+
if (!force && nonInteractive) throw new ZitadelError("E_VALIDATION", "Eject requires --force in non-interactive mode", { hint: "Re-run with --force to confirm deletion of managed files." });
|
|
76
|
+
const actions = await resolveEjectActions(cwd);
|
|
77
|
+
const removed = [];
|
|
78
|
+
const preserved = [];
|
|
79
|
+
const backedUp = [];
|
|
80
|
+
for (const rel of actions.markedFiles) {
|
|
81
|
+
const abs = join(cwd, rel);
|
|
82
|
+
if (!await pathExists(abs)) continue;
|
|
83
|
+
if (!(await readFile(abs, "utf8").catch(() => "")).includes("// zitadel-cli: managed-file v1")) {
|
|
84
|
+
preserved.push(rel);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (!dryRun) await rm(abs, { force: true });
|
|
88
|
+
removed.push(rel);
|
|
89
|
+
}
|
|
90
|
+
for (const rel of actions.rootConfigFiles) {
|
|
91
|
+
const abs = join(cwd, rel);
|
|
92
|
+
if (!await pathExists(abs)) continue;
|
|
93
|
+
if (!dryRun) await rm(abs, { force: true });
|
|
94
|
+
removed.push(rel);
|
|
95
|
+
}
|
|
96
|
+
for (const rel of actions.envBackups) {
|
|
97
|
+
const abs = join(cwd, rel);
|
|
98
|
+
if (!await pathExists(abs)) continue;
|
|
99
|
+
if (dryRun) {
|
|
100
|
+
backedUp.push(`${rel} -> ${rel}.ejected-<timestamp>`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const backup = `${abs}.ejected-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
104
|
+
await rename(abs, backup);
|
|
105
|
+
backedUp.push(`${rel} -> ${backup.slice(cwd.length + 1)}`);
|
|
106
|
+
}
|
|
107
|
+
for (const rel of actions.directories) {
|
|
108
|
+
const abs = join(cwd, rel);
|
|
109
|
+
if (!await pathExists(abs)) continue;
|
|
110
|
+
if (!dryRun) await rm(abs, {
|
|
111
|
+
recursive: true,
|
|
112
|
+
force: true
|
|
113
|
+
});
|
|
114
|
+
removed.push(rel);
|
|
115
|
+
}
|
|
116
|
+
if (removed.length === 0 && backedUp.length === 0) return this.emit({
|
|
117
|
+
status: "skipped",
|
|
118
|
+
reason: "nothing-to-eject",
|
|
119
|
+
data: { cwd }
|
|
120
|
+
});
|
|
121
|
+
const nextCommands = assembleNextCommands(backedUp, actions.dependencies);
|
|
122
|
+
return this.emit({
|
|
123
|
+
status: "ok",
|
|
124
|
+
data: {
|
|
125
|
+
title: "Zitadel ejected. Remote project is untouched.",
|
|
126
|
+
files_removed: removed,
|
|
127
|
+
files_preserved: preserved,
|
|
128
|
+
backed_up: backedUp,
|
|
129
|
+
next_commands: nextCommands
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
export { Eject as default };
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=eject.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eject.mjs","names":[],"sources":["../../src/commands/eject.ts"],"sourcesContent":["import { readFile, rename, rm, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { ZitadelError } from \"../lib/errors\";\nimport { createOrca } from \"../lib/orca\";\nimport type { EjectActions } from \"../lib/orca/patchers/types\";\nimport { MANAGED_MARKER } from \"../lib/paths\";\nimport { readRendererId, readZitadelConfig } from \"../lib/project\";\n\n/**\n * Asks the framework patcher which artifacts it owns. Falls back to the\n * framework-agnostic set (`zitadel.json`, `.zitadel/`, `.env.local`) when the\n * framework or its patcher cannot be resolved, so an orphaned/partial project\n * can still be cleaned up.\n */\nasync function resolveEjectActions(cwd: string): Promise<EjectActions> {\n const fallback: EjectActions = {\n markedFiles: [],\n rootConfigFiles: [\"zitadel.json\"],\n directories: [\".zitadel\"],\n envBackups: [\".env.local\"],\n dependencies: [],\n };\n const orca = createOrca();\n const framework = await orca.tryDetect(cwd);\n if (!framework) {\n return fallback;\n }\n try {\n const config = await readZitadelConfig(cwd).catch(() => ({}) as Record<string, unknown>);\n return orca.patcherFor(framework.id).artifacts({\n framework,\n rendererId: readRendererId(config),\n });\n } catch {\n return fallback;\n }\n}\n\n/**\n * Builds the `next_commands` envelope field — manual follow-ups `eject` can't\n * safely run itself: deleting the `.env.local.ejected-*` backups it created\n * (only when some were made), and uninstalling the SDK packages the patcher\n * added (the CLI never modifies the user's `package.json` + lockfile +\n * `node_modules` directly; it just suggests the command).\n */\nfunction assembleNextCommands(\n backedUp: ReadonlyArray<unknown>,\n dependencies: ReadonlyArray<string>,\n): ReadonlyArray<string> {\n const commands: string[] = [];\n if (backedUp.length > 0) {\n commands.push(\"rm -f .env.local.ejected-*\");\n }\n for (const dep of dependencies) {\n commands.push(`npm uninstall ${dep}`);\n }\n return commands;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * `zitadel eject` — remove managed files and local Zitadel state.\n *\n * Removes Zitadel-managed files from the project, leaving the remote project\n * untouched. The set of files comes from the framework patcher's\n * {@link import(\"../lib/orca/patchers/types\").Patcher.artifacts}, so the patcher\n * is the single source of truth for what its integration owns.\n *\n * Marked code files are removed only when they still carry the managed marker\n * (user-replaced files are preserved); `zitadel.json` is removed; `.env.local`\n * is renamed to a timestamped backup; and `.zitadel/` is removed wholesale.\n * `--dry-run` reports without touching the filesystem; non-interactive runs\n * require `--force`.\n */\nexport default class Eject extends BaseCommand {\n static override description = \"Remove managed files and local Zitadel state.\";\n static override aliases = [\"uninstall\"];\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Eject);\n await this.toMeta(flags);\n const { cwd, force, nonInteractive, dryRun } = this.meta;\n\n if (!force && nonInteractive) {\n throw new ZitadelError(\"E_VALIDATION\", \"Eject requires --force in non-interactive mode\", {\n hint: \"Re-run with --force to confirm deletion of managed files.\",\n });\n }\n\n const actions = await resolveEjectActions(cwd);\n const removed: string[] = [];\n const preserved: string[] = [];\n const backedUp: string[] = [];\n\n for (const rel of actions.markedFiles) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n const contents = await readFile(abs, \"utf8\").catch(() => \"\");\n if (!contents.includes(MANAGED_MARKER)) {\n preserved.push(rel);\n continue;\n }\n if (!dryRun) {\n await rm(abs, { force: true });\n }\n removed.push(rel);\n }\n\n for (const rel of actions.rootConfigFiles) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n if (!dryRun) {\n await rm(abs, { force: true });\n }\n removed.push(rel);\n }\n\n for (const rel of actions.envBackups) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n if (dryRun) {\n backedUp.push(`${rel} -> ${rel}.ejected-<timestamp>`);\n continue;\n }\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backup = `${abs}.ejected-${stamp}`;\n await rename(abs, backup);\n backedUp.push(`${rel} -> ${backup.slice(cwd.length + 1)}`);\n }\n\n for (const rel of actions.directories) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n if (!dryRun) {\n await rm(abs, { recursive: true, force: true });\n }\n removed.push(rel);\n }\n\n if (removed.length === 0 && backedUp.length === 0) {\n return this.emit({ status: \"skipped\", reason: \"nothing-to-eject\", data: { cwd } });\n }\n\n const nextCommands = assembleNextCommands(backedUp, actions.dependencies);\n\n return this.emit({\n status: \"ok\",\n data: {\n title: \"Zitadel ejected. Remote project is untouched.\",\n files_removed: removed,\n files_preserved: preserved,\n backed_up: backedUp,\n next_commands: nextCommands,\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { o as readZitadelSecret, s as BaseCommand } from "../project-C3pSfbao.mjs";
|
|
2
|
+
import { a as makeSyncers, n as summarizePlan, o as environmentSchema, r as buildSyncPlan, t as renderPlan } from "../sync-Cuyh-X1J.mjs";
|
|
3
|
+
import { Flags } from "@oclif/core";
|
|
4
|
+
import { consola as consola$1 } from "consola";
|
|
5
|
+
import { createZitadelClient } from "@zitadel/api/client";
|
|
6
|
+
//#region src/commands/plan.ts
|
|
7
|
+
/**
|
|
8
|
+
* `zitadel plan` — validate config and preview the sync diff without mutating.
|
|
9
|
+
*
|
|
10
|
+
* The read-only counterpart of `apply`: it builds and renders the diff instead
|
|
11
|
+
* of running the sync loop. All validation (structural shape and env-ref
|
|
12
|
+
* presence) happens in the sync engine, so an invalid file fails the same way
|
|
13
|
+
* `apply` would.
|
|
14
|
+
*/
|
|
15
|
+
var Plan = class Plan extends BaseCommand {
|
|
16
|
+
static description = "Validate config without mutation and preview the sync diff.";
|
|
17
|
+
static hidden = true;
|
|
18
|
+
static flags = { environment: Flags.string({
|
|
19
|
+
char: "e",
|
|
20
|
+
description: "Target environment (default: development).",
|
|
21
|
+
options: [...environmentSchema.options]
|
|
22
|
+
}) };
|
|
23
|
+
async run() {
|
|
24
|
+
const { flags } = await this.parse(Plan);
|
|
25
|
+
await this.toMeta(flags);
|
|
26
|
+
const { cwd, source, env, isTTY } = this.meta;
|
|
27
|
+
const secret = await readZitadelSecret(cwd);
|
|
28
|
+
consola$1.info(`Project ${secret.project_id}`);
|
|
29
|
+
consola$1.info(`Server ${source}`);
|
|
30
|
+
const syncers = makeSyncers({
|
|
31
|
+
client: createZitadelClient({
|
|
32
|
+
baseUrl: source,
|
|
33
|
+
token: secret.project_secret
|
|
34
|
+
}),
|
|
35
|
+
projectId: secret.project_id,
|
|
36
|
+
env
|
|
37
|
+
});
|
|
38
|
+
consola$1.start("Building plan");
|
|
39
|
+
const plan = await buildSyncPlan(cwd, syncers, true);
|
|
40
|
+
const summary = summarizePlan(plan);
|
|
41
|
+
consola$1.success(`Plan: ${summary.creates} create${summary.creates === 1 ? "" : "s"}, ${summary.updates} update${summary.updates === 1 ? "" : "s"}, ${summary.deletes} delete${summary.deletes === 1 ? "" : "s"}, ${summary.total - summary.creates - summary.updates - summary.deletes} unchanged`);
|
|
42
|
+
return this.emit({
|
|
43
|
+
status: "ok",
|
|
44
|
+
data: summary,
|
|
45
|
+
pretty: renderPlan(plan, isTTY)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
export { Plan as default };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=plan.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.mjs","names":[],"sources":["../../src/commands/plan.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\nimport { consola } from \"consola\";\n\nimport { createZitadelClient } from \"@zitadel/api/client\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { environmentSchema } from \"../lib/environment\";\nimport { buildSyncPlan, makeSyncers, renderPlan, summarizePlan } from \"../lib/sync\";\nimport { readZitadelSecret } from \"../lib/project\";\n\n/**\n * `zitadel plan` — validate config and preview the sync diff without mutating.\n *\n * The read-only counterpart of `apply`: it builds and renders the diff instead\n * of running the sync loop. All validation (structural shape and env-ref\n * presence) happens in the sync engine, so an invalid file fails the same way\n * `apply` would.\n */\nexport default class Plan extends BaseCommand {\n static override description = \"Validate config without mutation and preview the sync diff.\";\n // Temporarily hidden while we collapse the dev workflow around `setup`'s\n // auto-apply. The logic stays wired up so re-exposing this command is a\n // one-line flip when we settle on the surface area.\n static override hidden = true;\n static override flags = {\n environment: Flags.string({\n char: \"e\",\n description: \"Target environment (default: development).\",\n options: [...environmentSchema.options],\n }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Plan);\n await this.toMeta(flags);\n const { cwd, source, env, isTTY } = this.meta;\n\n const secret = await readZitadelSecret(cwd);\n consola.info(`Project ${secret.project_id}`);\n consola.info(`Server ${source}`);\n const client = createZitadelClient({\n baseUrl: source,\n token: secret.project_secret,\n });\n const syncers = makeSyncers({ client, projectId: secret.project_id, env });\n\n consola.start(\"Building plan\");\n const plan = await buildSyncPlan(cwd, syncers, true);\n const summary = summarizePlan(plan);\n consola.success(\n `Plan: ${summary.creates} create${summary.creates === 1 ? \"\" : \"s\"}, ` +\n `${summary.updates} update${summary.updates === 1 ? \"\" : \"s\"}, ` +\n `${summary.deletes} delete${summary.deletes === 1 ? \"\" : \"s\"}, ` +\n `${summary.total - summary.creates - summary.updates - summary.deletes} unchanged`,\n );\n return this.emit({\n status: \"ok\",\n data: summary,\n pretty: renderPlan(plan, isTTY),\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,IAAqB,OAArB,MAAqB,aAAa,YAAY;CAC5C,OAAgB,cAAc;CAI9B,OAAgB,SAAS;CACzB,OAAgB,QAAQ,EACtB,aAAa,MAAM,OAAO;EACxB,MAAM;EACN,aAAa;EACb,SAAS,CAAC,GAAG,kBAAkB,QAAQ;EACxC,CAAC,EACH;CAED,MAAM,MAA6B;EACjC,MAAM,EAAE,UAAU,MAAM,KAAK,MAAM,KAAK;AACxC,QAAM,KAAK,OAAO,MAAM;EACxB,MAAM,EAAE,KAAK,QAAQ,KAAK,UAAU,KAAK;EAEzC,MAAM,SAAS,MAAM,kBAAkB,IAAI;AAC3C,YAAQ,KAAK,aAAa,OAAO,aAAa;AAC9C,YAAQ,KAAK,aAAa,SAAS;EAKnC,MAAM,UAAU,YAAY;GAAE,QAJf,oBAAoB;IACjC,SAAS;IACT,OAAO,OAAO;IACf,CACmC;GAAE,WAAW,OAAO;GAAY;GAAK,CAAC;AAE1E,YAAQ,MAAM,gBAAgB;EAC9B,MAAM,OAAO,MAAM,cAAc,KAAK,SAAS,KAAK;EACpD,MAAM,UAAU,cAAc,KAAK;AACnC,YAAQ,QACN,SAAS,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC9D,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC1D,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC1D,QAAQ,QAAQ,QAAQ,UAAU,QAAQ,UAAU,QAAQ,QAAQ,YAC1E;AACD,SAAO,KAAK,KAAK;GACf,QAAQ;GACR,MAAM;GACN,QAAQ,WAAW,MAAM,MAAM;GAChC,CAAC"}
|