@vibesdotdev/infra-deploy 0.0.1

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.
Files changed (183) hide show
  1. package/README.md +125 -0
  2. package/SPEC.md +181 -0
  3. package/dist/cli/alerts/infra-alerts-create.cli-command.descriptor.d.ts +39 -0
  4. package/dist/cli/alerts/infra-alerts-create.cli-command.descriptor.d.ts.map +1 -0
  5. package/dist/cli/alerts/infra-alerts-create.cli-command.descriptor.js +61 -0
  6. package/dist/cli/alerts/infra-alerts-create.cli-command.descriptor.js.map +1 -0
  7. package/dist/cli/alerts/infra-alerts-create.cli-command.impl.d.ts +13 -0
  8. package/dist/cli/alerts/infra-alerts-create.cli-command.impl.d.ts.map +1 -0
  9. package/dist/cli/alerts/infra-alerts-create.cli-command.impl.js +109 -0
  10. package/dist/cli/alerts/infra-alerts-create.cli-command.impl.js.map +1 -0
  11. package/dist/cli/alerts/infra-alerts-delete.cli-command.descriptor.d.ts +31 -0
  12. package/dist/cli/alerts/infra-alerts-delete.cli-command.descriptor.d.ts.map +1 -0
  13. package/dist/cli/alerts/infra-alerts-delete.cli-command.descriptor.js +36 -0
  14. package/dist/cli/alerts/infra-alerts-delete.cli-command.descriptor.js.map +1 -0
  15. package/dist/cli/alerts/infra-alerts-delete.cli-command.impl.d.ts +11 -0
  16. package/dist/cli/alerts/infra-alerts-delete.cli-command.impl.d.ts.map +1 -0
  17. package/dist/cli/alerts/infra-alerts-delete.cli-command.impl.js +67 -0
  18. package/dist/cli/alerts/infra-alerts-delete.cli-command.impl.js.map +1 -0
  19. package/dist/cli/alerts/infra-alerts-list.cli-command.descriptor.d.ts +21 -0
  20. package/dist/cli/alerts/infra-alerts-list.cli-command.descriptor.d.ts.map +1 -0
  21. package/dist/cli/alerts/infra-alerts-list.cli-command.descriptor.js +27 -0
  22. package/dist/cli/alerts/infra-alerts-list.cli-command.descriptor.js.map +1 -0
  23. package/dist/cli/alerts/infra-alerts-list.cli-command.impl.d.ts +12 -0
  24. package/dist/cli/alerts/infra-alerts-list.cli-command.impl.d.ts.map +1 -0
  25. package/dist/cli/alerts/infra-alerts-list.cli-command.impl.js +74 -0
  26. package/dist/cli/alerts/infra-alerts-list.cli-command.impl.js.map +1 -0
  27. package/dist/cli/alerts/infra-alerts.cli-group.descriptor.d.ts +12 -0
  28. package/dist/cli/alerts/infra-alerts.cli-group.descriptor.d.ts.map +1 -0
  29. package/dist/cli/alerts/infra-alerts.cli-group.descriptor.js +12 -0
  30. package/dist/cli/alerts/infra-alerts.cli-group.descriptor.js.map +1 -0
  31. package/dist/cli/audit/infra-audit.cli-command.descriptor.d.ts +21 -0
  32. package/dist/cli/audit/infra-audit.cli-command.descriptor.d.ts.map +1 -0
  33. package/dist/cli/audit/infra-audit.cli-command.descriptor.js +28 -0
  34. package/dist/cli/audit/infra-audit.cli-command.descriptor.js.map +1 -0
  35. package/dist/cli/audit/infra-audit.cli-command.impl.d.ts +18 -0
  36. package/dist/cli/audit/infra-audit.cli-command.impl.d.ts.map +1 -0
  37. package/dist/cli/audit/infra-audit.cli-command.impl.js +219 -0
  38. package/dist/cli/audit/infra-audit.cli-command.impl.js.map +1 -0
  39. package/dist/cli/infra-deploy.cli-group.descriptor.d.ts +12 -0
  40. package/dist/cli/infra-deploy.cli-group.descriptor.d.ts.map +1 -0
  41. package/dist/cli/infra-deploy.cli-group.descriptor.js +12 -0
  42. package/dist/cli/infra-deploy.cli-group.descriptor.js.map +1 -0
  43. package/dist/cli/infra.cli-group.descriptor.d.ts +11 -0
  44. package/dist/cli/infra.cli-group.descriptor.d.ts.map +1 -0
  45. package/dist/cli/infra.cli-group.descriptor.js +11 -0
  46. package/dist/cli/infra.cli-group.descriptor.js.map +1 -0
  47. package/dist/cli/list/infra-deploy.list.cli-command.descriptor.d.ts +34 -0
  48. package/dist/cli/list/infra-deploy.list.cli-command.descriptor.d.ts.map +1 -0
  49. package/dist/cli/list/infra-deploy.list.cli-command.descriptor.js +29 -0
  50. package/dist/cli/list/infra-deploy.list.cli-command.descriptor.js.map +1 -0
  51. package/dist/cli/list/infra-deploy.list.cli-command.impl.d.ts +5 -0
  52. package/dist/cli/list/infra-deploy.list.cli-command.impl.d.ts.map +1 -0
  53. package/dist/cli/list/infra-deploy.list.cli-command.impl.js +110 -0
  54. package/dist/cli/list/infra-deploy.list.cli-command.impl.js.map +1 -0
  55. package/dist/cli/logs/infra-logs.cli-command.descriptor.d.ts +39 -0
  56. package/dist/cli/logs/infra-logs.cli-command.descriptor.d.ts.map +1 -0
  57. package/dist/cli/logs/infra-logs.cli-command.descriptor.js +64 -0
  58. package/dist/cli/logs/infra-logs.cli-command.descriptor.js.map +1 -0
  59. package/dist/cli/logs/infra-logs.cli-command.impl.d.ts +5 -0
  60. package/dist/cli/logs/infra-logs.cli-command.impl.d.ts.map +1 -0
  61. package/dist/cli/logs/infra-logs.cli-command.impl.js +323 -0
  62. package/dist/cli/logs/infra-logs.cli-command.impl.js.map +1 -0
  63. package/dist/cli/logs/stream-worker-tail.d.ts +62 -0
  64. package/dist/cli/logs/stream-worker-tail.d.ts.map +1 -0
  65. package/dist/cli/logs/stream-worker-tail.js +165 -0
  66. package/dist/cli/logs/stream-worker-tail.js.map +1 -0
  67. package/dist/cli/npm/infra-npm-publish.cli-command.descriptor.d.ts +27 -0
  68. package/dist/cli/npm/infra-npm-publish.cli-command.descriptor.d.ts.map +1 -0
  69. package/dist/cli/npm/infra-npm-publish.cli-command.descriptor.js +75 -0
  70. package/dist/cli/npm/infra-npm-publish.cli-command.descriptor.js.map +1 -0
  71. package/dist/cli/npm/infra-npm-publish.cli-command.impl.d.ts +5 -0
  72. package/dist/cli/npm/infra-npm-publish.cli-command.impl.d.ts.map +1 -0
  73. package/dist/cli/npm/infra-npm-publish.cli-command.impl.js +383 -0
  74. package/dist/cli/npm/infra-npm-publish.cli-command.impl.js.map +1 -0
  75. package/dist/cli/npm/infra-npm.cli-group.descriptor.d.ts +12 -0
  76. package/dist/cli/npm/infra-npm.cli-group.descriptor.d.ts.map +1 -0
  77. package/dist/cli/npm/infra-npm.cli-group.descriptor.js +12 -0
  78. package/dist/cli/npm/infra-npm.cli-group.descriptor.js.map +1 -0
  79. package/dist/cli/observability/infra-observability-set.cli-command.descriptor.d.ts +25 -0
  80. package/dist/cli/observability/infra-observability-set.cli-command.descriptor.d.ts.map +1 -0
  81. package/dist/cli/observability/infra-observability-set.cli-command.descriptor.js +57 -0
  82. package/dist/cli/observability/infra-observability-set.cli-command.descriptor.js.map +1 -0
  83. package/dist/cli/observability/infra-observability-set.cli-command.impl.d.ts +17 -0
  84. package/dist/cli/observability/infra-observability-set.cli-command.impl.d.ts.map +1 -0
  85. package/dist/cli/observability/infra-observability-set.cli-command.impl.js +152 -0
  86. package/dist/cli/observability/infra-observability-set.cli-command.impl.js.map +1 -0
  87. package/dist/cli/observability/infra-observability-status.cli-command.descriptor.d.ts +21 -0
  88. package/dist/cli/observability/infra-observability-status.cli-command.descriptor.d.ts.map +1 -0
  89. package/dist/cli/observability/infra-observability-status.cli-command.descriptor.js +35 -0
  90. package/dist/cli/observability/infra-observability-status.cli-command.descriptor.js.map +1 -0
  91. package/dist/cli/observability/infra-observability-status.cli-command.impl.d.ts +17 -0
  92. package/dist/cli/observability/infra-observability-status.cli-command.impl.d.ts.map +1 -0
  93. package/dist/cli/observability/infra-observability-status.cli-command.impl.js +99 -0
  94. package/dist/cli/observability/infra-observability-status.cli-command.impl.js.map +1 -0
  95. package/dist/cli/observability/infra-observability.cli-group.descriptor.d.ts +12 -0
  96. package/dist/cli/observability/infra-observability.cli-group.descriptor.d.ts.map +1 -0
  97. package/dist/cli/observability/infra-observability.cli-group.descriptor.js +12 -0
  98. package/dist/cli/observability/infra-observability.cli-group.descriptor.js.map +1 -0
  99. package/dist/cli/regenerate/infra-deploy.regenerate.cli-command.descriptor.d.ts +27 -0
  100. package/dist/cli/regenerate/infra-deploy.regenerate.cli-command.descriptor.d.ts.map +1 -0
  101. package/dist/cli/regenerate/infra-deploy.regenerate.cli-command.descriptor.js +35 -0
  102. package/dist/cli/regenerate/infra-deploy.regenerate.cli-command.descriptor.js.map +1 -0
  103. package/dist/cli/regenerate/infra-deploy.regenerate.cli-command.impl.d.ts +5 -0
  104. package/dist/cli/regenerate/infra-deploy.regenerate.cli-command.impl.d.ts.map +1 -0
  105. package/dist/cli/regenerate/infra-deploy.regenerate.cli-command.impl.js +99 -0
  106. package/dist/cli/regenerate/infra-deploy.regenerate.cli-command.impl.js.map +1 -0
  107. package/dist/cli/rum/infra-rum-status.cli-command.descriptor.d.ts +21 -0
  108. package/dist/cli/rum/infra-rum-status.cli-command.descriptor.d.ts.map +1 -0
  109. package/dist/cli/rum/infra-rum-status.cli-command.descriptor.js +27 -0
  110. package/dist/cli/rum/infra-rum-status.cli-command.descriptor.js.map +1 -0
  111. package/dist/cli/rum/infra-rum-status.cli-command.impl.d.ts +12 -0
  112. package/dist/cli/rum/infra-rum-status.cli-command.impl.d.ts.map +1 -0
  113. package/dist/cli/rum/infra-rum-status.cli-command.impl.js +88 -0
  114. package/dist/cli/rum/infra-rum-status.cli-command.impl.js.map +1 -0
  115. package/dist/cli/rum/infra-rum.cli-group.descriptor.d.ts +12 -0
  116. package/dist/cli/rum/infra-rum.cli-group.descriptor.d.ts.map +1 -0
  117. package/dist/cli/rum/infra-rum.cli-group.descriptor.js +12 -0
  118. package/dist/cli/rum/infra-rum.cli-group.descriptor.js.map +1 -0
  119. package/dist/cli/run/infra-deploy.run.cli-command.descriptor.d.ts +27 -0
  120. package/dist/cli/run/infra-deploy.run.cli-command.descriptor.d.ts.map +1 -0
  121. package/dist/cli/run/infra-deploy.run.cli-command.descriptor.js +49 -0
  122. package/dist/cli/run/infra-deploy.run.cli-command.descriptor.js.map +1 -0
  123. package/dist/cli/run/infra-deploy.run.cli-command.impl.d.ts +5 -0
  124. package/dist/cli/run/infra-deploy.run.cli-command.impl.d.ts.map +1 -0
  125. package/dist/cli/run/infra-deploy.run.cli-command.impl.js +272 -0
  126. package/dist/cli/run/infra-deploy.run.cli-command.impl.js.map +1 -0
  127. package/dist/cli/shared/discover-deployments.d.ts +41 -0
  128. package/dist/cli/shared/discover-deployments.d.ts.map +1 -0
  129. package/dist/cli/shared/discover-deployments.js +95 -0
  130. package/dist/cli/shared/discover-deployments.js.map +1 -0
  131. package/dist/config-loader.d.ts +24 -0
  132. package/dist/config-loader.d.ts.map +1 -0
  133. package/dist/config-loader.js +135 -0
  134. package/dist/config-loader.js.map +1 -0
  135. package/dist/index.d.ts +5 -0
  136. package/dist/index.d.ts.map +1 -0
  137. package/dist/index.js +4 -0
  138. package/dist/index.js.map +1 -0
  139. package/dist/infra-deploy.plugin.d.ts +3 -0
  140. package/dist/infra-deploy.plugin.d.ts.map +1 -0
  141. package/dist/infra-deploy.plugin.js +59 -0
  142. package/dist/infra-deploy.plugin.js.map +1 -0
  143. package/dist/regenerate.d.ts +55 -0
  144. package/dist/regenerate.d.ts.map +1 -0
  145. package/dist/regenerate.js +206 -0
  146. package/dist/regenerate.js.map +1 -0
  147. package/package.json +85 -0
  148. package/src/cli/alerts/infra-alerts-create.cli-command.descriptor.ts +61 -0
  149. package/src/cli/alerts/infra-alerts-create.cli-command.impl.ts +131 -0
  150. package/src/cli/alerts/infra-alerts-delete.cli-command.descriptor.ts +36 -0
  151. package/src/cli/alerts/infra-alerts-delete.cli-command.impl.ts +75 -0
  152. package/src/cli/alerts/infra-alerts-list.cli-command.descriptor.ts +27 -0
  153. package/src/cli/alerts/infra-alerts-list.cli-command.impl.ts +88 -0
  154. package/src/cli/alerts/infra-alerts.cli-group.descriptor.ts +12 -0
  155. package/src/cli/audit/infra-audit.cli-command.descriptor.ts +28 -0
  156. package/src/cli/audit/infra-audit.cli-command.impl.ts +293 -0
  157. package/src/cli/infra-deploy.cli-group.descriptor.ts +12 -0
  158. package/src/cli/infra.cli-group.descriptor.ts +11 -0
  159. package/src/cli/list/infra-deploy.list.cli-command.descriptor.ts +29 -0
  160. package/src/cli/list/infra-deploy.list.cli-command.impl.ts +125 -0
  161. package/src/cli/logs/infra-logs.cli-command.descriptor.ts +65 -0
  162. package/src/cli/logs/infra-logs.cli-command.impl.ts +354 -0
  163. package/src/cli/logs/stream-worker-tail.ts +202 -0
  164. package/src/cli/npm/infra-npm-publish.cli-command.descriptor.ts +75 -0
  165. package/src/cli/npm/infra-npm-publish.cli-command.impl.ts +474 -0
  166. package/src/cli/npm/infra-npm.cli-group.descriptor.ts +12 -0
  167. package/src/cli/observability/infra-observability-set.cli-command.descriptor.ts +57 -0
  168. package/src/cli/observability/infra-observability-set.cli-command.impl.ts +173 -0
  169. package/src/cli/observability/infra-observability-status.cli-command.descriptor.ts +35 -0
  170. package/src/cli/observability/infra-observability-status.cli-command.impl.ts +124 -0
  171. package/src/cli/observability/infra-observability.cli-group.descriptor.ts +12 -0
  172. package/src/cli/regenerate/infra-deploy.regenerate.cli-command.descriptor.ts +36 -0
  173. package/src/cli/regenerate/infra-deploy.regenerate.cli-command.impl.ts +103 -0
  174. package/src/cli/rum/infra-rum-status.cli-command.descriptor.ts +27 -0
  175. package/src/cli/rum/infra-rum-status.cli-command.impl.ts +112 -0
  176. package/src/cli/rum/infra-rum.cli-group.descriptor.ts +12 -0
  177. package/src/cli/run/infra-deploy.run.cli-command.descriptor.ts +49 -0
  178. package/src/cli/run/infra-deploy.run.cli-command.impl.ts +330 -0
  179. package/src/cli/shared/discover-deployments.ts +127 -0
  180. package/src/config-loader.ts +179 -0
  181. package/src/index.ts +11 -0
  182. package/src/infra-deploy.plugin.ts +83 -0
  183. package/src/regenerate.ts +230 -0
@@ -0,0 +1,330 @@
1
+ import { getVibesRuntime } from '@vibesdotdev/runtime';
2
+ import type { UIContext } from '@vibesdotdev/cli/providers';
3
+ import { resolveCurrentEnvironmentConfig } from '@vibesdotdev/config/environment/current';
4
+ import { resolve } from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { getCloudflareWorkersDeployClient } from '@vibesdotdev/connector-cloudflare/client';
7
+ import { getDigitalOceanAppDeployClient } from '@vibesdotdev/infra-doks/client';
8
+ import { loadDeploymentConfig } from '../../config-loader.ts';
9
+ import { regenerateApp } from '../../regenerate.ts';
10
+
11
+ function readString(v: unknown): string | undefined {
12
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
13
+ }
14
+ function readBoolean(v: unknown): boolean {
15
+ return v === true || v === 'true';
16
+ }
17
+
18
+ function resolveWorkspaceRoot(
19
+ workspaceRootDir: string | undefined,
20
+ appDir: string,
21
+ override: string | undefined
22
+ ): string {
23
+ if (override) return resolve(override);
24
+ if (!workspaceRootDir || workspaceRootDir === '.') return process.cwd();
25
+ return resolve(appDir, workspaceRootDir);
26
+ }
27
+
28
+ const CLOUDFLARE_API_TOKEN_KEY = 'CLOUDFLARE_API_TOKEN';
29
+ const CLOUDFLARE_ACCOUNT_ID_KEY = 'CLOUDFLARE_ACCOUNT_ID';
30
+
31
+ async function resolveCloudflareEnvFromSecretsStore(
32
+ runtime: ReturnType<typeof getVibesRuntime>,
33
+ ui: UIContext,
34
+ environmentOverride?: string
35
+ ): Promise<Record<string, string>> {
36
+ if (process.env.CLOUDFLARE_API_TOKEN && process.env.CLOUDFLARE_ACCOUNT_ID) {
37
+ return {};
38
+ }
39
+ if (!runtime.hasKind('secrets/store')) {
40
+ throw new Error(
41
+ `Missing secrets/store kind in runtime. Enable secrets backends via plugin config ` +
42
+ `(for example plugins.auth.secrets=true and plugins.auth.secretsBackends.*) before running deploy.`
43
+ );
44
+ }
45
+
46
+ let envName = environmentOverride ?? 'local';
47
+ let envTier = 'local';
48
+ if (environmentOverride) {
49
+ envTier = environmentOverride;
50
+ } else {
51
+ try {
52
+ const envConfig = await resolveCurrentEnvironmentConfig();
53
+ envName = envConfig.name;
54
+ envTier = envConfig.tier ?? 'local';
55
+ } catch {
56
+ // Fall back to local when config environment resolution is unavailable.
57
+ }
58
+ }
59
+
60
+ const descriptors = runtime.assets('secrets/store').descriptors() as Array<{
61
+ id: string;
62
+ priority?: number;
63
+ tiers?: string[];
64
+ }>;
65
+ const tierCandidates = new Set([envTier]);
66
+ if (envTier === 'local' || envTier === 'dev') {
67
+ // Deploy auth is often run from local shells while credentials live in
68
+ // staging/production backends (Vault/Cloudflare). Include those tiers as
69
+ // fallbacks instead of hard-filtering them out.
70
+ tierCandidates.add('staging');
71
+ tierCandidates.add('production');
72
+ }
73
+ const candidates = descriptors
74
+ .filter((d) => {
75
+ if (environmentOverride) return true;
76
+ if (!d.tiers || d.tiers.length === 0) return true;
77
+ return d.tiers.some((tier) => tierCandidates.has(tier));
78
+ })
79
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
80
+
81
+ const out: Record<string, string> = {};
82
+ for (const descriptor of candidates) {
83
+ const store = (await runtime
84
+ .query('secrets/store')
85
+ .withId(descriptor.id)
86
+ .resolve()) as {
87
+ get(environment: string, key: string): Promise<string | undefined>;
88
+ };
89
+
90
+ if (!process.env.CLOUDFLARE_API_TOKEN && !out.CLOUDFLARE_API_TOKEN) {
91
+ const value = await store.get(envName, CLOUDFLARE_API_TOKEN_KEY);
92
+ if (value && value.length > 0) {
93
+ out.CLOUDFLARE_API_TOKEN = value;
94
+ }
95
+ }
96
+
97
+ if (!process.env.CLOUDFLARE_ACCOUNT_ID && !out.CLOUDFLARE_ACCOUNT_ID) {
98
+ const value = await store.get(envName, CLOUDFLARE_ACCOUNT_ID_KEY);
99
+ if (value && value.length > 0) {
100
+ out.CLOUDFLARE_ACCOUNT_ID = value;
101
+ }
102
+ }
103
+
104
+ if (out.CLOUDFLARE_API_TOKEN && out.CLOUDFLARE_ACCOUNT_ID) break;
105
+ }
106
+
107
+ if (Object.keys(out).length > 0) {
108
+ ui.info(
109
+ `Resolved Cloudflare deploy credentials from secrets store (${envName}/${envTier}).`
110
+ );
111
+ }
112
+ return out;
113
+ }
114
+
115
+ export default {
116
+ async execute(args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
117
+ const runtime = getVibesRuntime();
118
+ const ui = (await runtime.context('cli/ui')) as UIContext;
119
+
120
+ const pathArg = readString(args.path) ?? process.cwd();
121
+ const output = readString(opts.output) ?? 'text';
122
+ const skipBuild = readBoolean(opts.skipBuild);
123
+ const skipD1Migrations = readBoolean(opts.skipD1Migrations);
124
+ const dryRun = readBoolean(opts.dryRun);
125
+ const workspaceRootOverride = readString(opts.workspaceRoot);
126
+ const targetEnvironment = readString(opts.environment);
127
+ let loaded;
128
+ try {
129
+ loaded = await loadDeploymentConfig(pathArg);
130
+ } catch (err) {
131
+ ui.error(`Failed to load deployment config: ${(err as Error).message}`);
132
+ process.exitCode = 1;
133
+ return;
134
+ }
135
+
136
+ const { deployment, provider, appDir } = loaded;
137
+ if (output === 'text') {
138
+ ui.info(`Deploying ${deployment.appName} (${deployment.appId}) via ${provider}`);
139
+ }
140
+
141
+ const onLine = (_stream: 'stdout' | 'stderr', line: string) => {
142
+ if (output === 'text') ui.log(line);
143
+ };
144
+
145
+ try {
146
+ switch (provider) {
147
+ case 'cloudflare-workers': {
148
+ const environment = readString(opts.wranglerEnv);
149
+ const workspaceRoot = resolveWorkspaceRoot(
150
+ deployment.build.workspaceRootDir,
151
+ appDir,
152
+ workspaceRootOverride
153
+ );
154
+
155
+ // Run generic pre-deploy commands (provider-agnostic hooks)
156
+ if (deployment.preDeployCommands.length > 0) {
157
+ onLine('stdout', `Running ${deployment.preDeployCommands.length} pre-deploy command(s)...`);
158
+ for (const cmd of deployment.preDeployCommands) {
159
+ if (cmd.description) {
160
+ onLine('stdout', ` ${cmd.description}`);
161
+ }
162
+ if (dryRun) {
163
+ onLine('stdout', ` [dry-run] ${cmd.command}`);
164
+ continue;
165
+ }
166
+ const [bin, ...args] = cmd.command.split(' ').filter(Boolean);
167
+ if (!bin) continue;
168
+ const child = spawnSync(bin, args, {
169
+ cwd: appDir,
170
+ stdio: 'inherit',
171
+ env: process.env
172
+ });
173
+ if (child.status !== 0) {
174
+ throw new Error(
175
+ `Pre-deploy command failed: ${cmd.command} (exit=${child.status ?? child.error?.message ?? 'unknown'})`
176
+ );
177
+ }
178
+ }
179
+ onLine('stdout', 'Pre-deploy commands complete.');
180
+ }
181
+
182
+ // Regen gate: refuse to deploy a stale wrangler.jsonc. For apps with
183
+ // `managed: true` in deployment.config.ts, this enforces that what's
184
+ // on disk matches what the typed config would produce. Unmanaged apps
185
+ // pass through (the gate is opt-in until every app is migrated).
186
+ const regen = await regenerateApp(appDir, { mode: 'check' });
187
+ if (regen.status === 'drift') {
188
+ const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
189
+ const isKvIdDrift =
190
+ regen.diff &&
191
+ /^[\+\-]\s+"id":\s*"[^"]*"/m.test(regen.diff) &&
192
+ !uuidRe.test(regen.diff.replace(/"id":\s*"([^"]*)"/g, '$1').slice(0, 36));
193
+ if (!isKvIdDrift) {
194
+ const detail = regen.diff ? `\n${regen.diff}` : '';
195
+ throw new Error(
196
+ `wrangler.jsonc drift for ${regen.appId} — regenerate before deploy (run \`vibes infra deploy regenerate ${appDir}\`)${detail}`
197
+ );
198
+ }
199
+ onLine('stdout', `[regen] wrangler.jsonc verified (unchanged — kv namespace IDs resolved)`);
200
+ }
201
+ if (regen.status === 'error') {
202
+ throw new Error(
203
+ `regenerate check failed for ${appDir}: ${regen.reason ?? 'unknown error'}`
204
+ );
205
+ }
206
+ if (regen.status === 'updated' || regen.status === 'unchanged') {
207
+ onLine('stdout', `[regen] wrangler.jsonc verified (${regen.status})`);
208
+ } else if (regen.status === 'skipped') {
209
+ onLine('stdout', `[regen] skipped: ${regen.reason ?? 'not managed'}`);
210
+ }
211
+
212
+ const client = await getCloudflareWorkersDeployClient();
213
+ const envOverrides = await resolveCloudflareEnvFromSecretsStore(
214
+ runtime,
215
+ ui,
216
+ targetEnvironment
217
+ );
218
+
219
+ // Provisioning for KV, D1, R2 is now always attempted when declared
220
+ // (creation + ID reconciliation happens in the client using REST API).
221
+ // Migrations remain opt-in via autoMigrateD1 for safety.
222
+ const rawConfig = loaded.raw as Record<string, unknown>;
223
+ const autoMigrateD1 = rawConfig.autoMigrateD1 === true && !skipD1Migrations;
224
+ if (skipD1Migrations) {
225
+ onLine('stdout', 'Skipping D1 migrations for this deploy.');
226
+ }
227
+ const generatedWranglerConfig =
228
+ typeof rawConfig.generateWranglerConfig === 'function'
229
+ ? (rawConfig.generateWranglerConfig() as Record<string, unknown>)
230
+ : loaded.wranglerJsoncString
231
+ ? (JSON.parse(
232
+ loaded.wranglerJsoncString.replace(/^\s*\/\/.*$/gm, '')
233
+ ) as Record<string, unknown>)
234
+ : rawConfig;
235
+
236
+ const d1Databases =
237
+ ((rawConfig.d1Databases ??
238
+ rawConfig.d1_databases ??
239
+ generatedWranglerConfig.d1Databases ??
240
+ generatedWranglerConfig.d1_databases) as
241
+ | Array<{ binding: string; database_name: string; migrations_dir?: string }>
242
+ | undefined) ?? undefined;
243
+
244
+ const r2Buckets = (rawConfig.r2_buckets as
245
+ | Array<{ binding?: string; bucket_name: string }>
246
+ | undefined) ?? undefined;
247
+
248
+ const result = await client.deployWorker({
249
+ appDir,
250
+ workspaceRoot,
251
+ workerName: deployment.appId,
252
+ buildCommand: deployment.build.buildCommand,
253
+ skipBuild,
254
+ dryRun,
255
+ environment,
256
+ envOverrides,
257
+ kvNamespaces: (deployment as Record<string, unknown>).kvNamespaces as
258
+ | Array<{ binding: string; id: string; preview_id?: string }>
259
+ | undefined,
260
+ d1Databases: autoMigrateD1 ? d1Databases : undefined,
261
+ r2Buckets,
262
+ onLine
263
+ });
264
+
265
+ if (output === 'json') {
266
+ ui.json({
267
+ ok: true,
268
+ provider: 'cloudflare-workers',
269
+ workerName: result.workerName,
270
+ deployedUrl: result.deployedUrl
271
+ });
272
+ } else if (!dryRun) {
273
+ ui.success(
274
+ result.deployedUrl
275
+ ? `Deployed Worker ${result.workerName} at ${result.deployedUrl}`
276
+ : `Deployed Worker ${result.workerName}`
277
+ );
278
+ }
279
+ return;
280
+ }
281
+ case 'digitalocean-app': {
282
+ const doAppId = readString(opts.doAppId);
283
+ const doRegion = readString(opts.doRegion);
284
+ const doGitRepo = readString(opts.doGitRepo);
285
+ const doGitBranch = readString(opts.doGitBranch);
286
+
287
+ const client = await getDigitalOceanAppDeployClient();
288
+ const result = await client.deployApp({
289
+ deployment,
290
+ appId: doAppId,
291
+ region: doRegion,
292
+ git: doGitRepo ? { repoCloneUrl: doGitRepo, branch: doGitBranch ?? 'main' } : undefined,
293
+ dryRun,
294
+ onLine
295
+ });
296
+
297
+ if (output === 'json') {
298
+ ui.json({
299
+ ok: true,
300
+ provider: 'digitalocean-app',
301
+ appId: result.appId,
302
+ liveUrl: result.liveUrl
303
+ });
304
+ } else if (!dryRun) {
305
+ ui.success(
306
+ result.liveUrl
307
+ ? `Deployed DO App ${result.appId} at ${result.liveUrl}`
308
+ : `Deployed DO App ${result.appId}`
309
+ );
310
+ }
311
+ return;
312
+ }
313
+ default: {
314
+ ui.error(`Unsupported provider: ${provider}`);
315
+ ui.info('Supported providers: cloudflare-workers, digitalocean-app');
316
+ process.exitCode = 1;
317
+ return;
318
+ }
319
+ }
320
+ } catch (err) {
321
+ const message = (err as Error).message;
322
+ if (output === 'json') {
323
+ ui.json({ ok: false, error: message });
324
+ } else {
325
+ ui.error(message);
326
+ }
327
+ process.exitCode = 1;
328
+ }
329
+ }
330
+ };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Discover deployed apps in a workspace by scanning for `deployment.config.ts`
3
+ * files. Used by infra CLI commands that need the live inventory of deployed
4
+ * workers/sites without hardcoding app names in framework packages.
5
+ */
6
+
7
+ import { readdir, stat } from 'node:fs/promises';
8
+ import { join, isAbsolute, resolve } from 'node:path';
9
+ import type { DeploymentDependency } from '@vibesdotdev/infra-core/deployment';
10
+ import { loadDeploymentConfig } from '../../config-loader.ts';
11
+
12
+ const CONFIG_NAMES = new Set([
13
+ 'deployment.config.ts',
14
+ 'deployment.config.js',
15
+ 'deployment.config.mjs'
16
+ ]);
17
+
18
+ const IGNORED_DIRS = new Set([
19
+ 'node_modules',
20
+ '.git',
21
+ '.svelte-kit',
22
+ 'dist',
23
+ '.next',
24
+ '.turbo',
25
+ '.vite',
26
+ '.wrangler'
27
+ ]);
28
+
29
+ export interface DiscoveredDeployment {
30
+ appId: string;
31
+ appName: string;
32
+ provider: string;
33
+ appDir: string;
34
+ workerName: string | null;
35
+ dependsOn: DeploymentDependency[];
36
+ }
37
+
38
+ export interface DiscoverErrorInfo {
39
+ configPath: string;
40
+ appDir: string;
41
+ error: Error;
42
+ }
43
+
44
+ export interface DiscoverOptions {
45
+ /** Root to scan. Absolute, or relative to cwd. Defaults to cwd. */
46
+ root?: string;
47
+ /** Max directory depth. Defaults to 4. */
48
+ maxDepth?: number;
49
+ /** When set, only return deployments whose provider matches. */
50
+ provider?: string;
51
+ /**
52
+ * Invoked once per config that fails to load. The default behavior is to
53
+ * silently skip — `vibes infra deploy list` overrides this to surface
54
+ * unparseable rows.
55
+ */
56
+ onError?: (info: DiscoverErrorInfo) => void;
57
+ }
58
+
59
+ async function findConfigs(root: string, maxDepth: number): Promise<string[]> {
60
+ const found: string[] = [];
61
+ async function walk(dir: string, depth: number): Promise<void> {
62
+ if (depth > maxDepth) return;
63
+ let entries: string[];
64
+ try {
65
+ entries = await readdir(dir);
66
+ } catch {
67
+ return;
68
+ }
69
+ for (const entry of entries) {
70
+ if (CONFIG_NAMES.has(entry)) found.push(join(dir, entry));
71
+ }
72
+ for (const entry of entries) {
73
+ if (IGNORED_DIRS.has(entry) || entry.startsWith('.')) continue;
74
+ const full = join(dir, entry);
75
+ let info;
76
+ try {
77
+ info = await stat(full);
78
+ } catch {
79
+ continue;
80
+ }
81
+ if (info.isDirectory()) await walk(full, depth + 1);
82
+ }
83
+ }
84
+ await walk(root, 0);
85
+ return found;
86
+ }
87
+
88
+ /**
89
+ * Scan `root` for every `deployment.config.ts` and return the loaded
90
+ * deployment descriptors. Configs that fail to load are skipped; pass
91
+ * `onError` to be notified about each failure (e.g. to render
92
+ * `(unparseable)` rows in the list command).
93
+ */
94
+ export async function discoverDeployments(
95
+ opts: DiscoverOptions = {}
96
+ ): Promise<DiscoveredDeployment[]> {
97
+ const rootInput = opts.root ?? process.cwd();
98
+ const root = isAbsolute(rootInput) ? rootInput : resolve(process.cwd(), rootInput);
99
+ const maxDepth = opts.maxDepth ?? 4;
100
+
101
+ const configPaths = await findConfigs(root, maxDepth);
102
+ const results: DiscoveredDeployment[] = [];
103
+
104
+ for (const configPath of configPaths) {
105
+ const appDir = configPath.replace(/\/deployment\.config\.(ts|js|mjs)$/, '');
106
+ try {
107
+ const loaded = await loadDeploymentConfig(appDir);
108
+ if (opts.provider && loaded.provider !== opts.provider) continue;
109
+ results.push({
110
+ appId: loaded.deployment.appId,
111
+ appName: loaded.deployment.appName,
112
+ provider: loaded.provider,
113
+ appDir: loaded.appDir,
114
+ workerName: loaded.workerName,
115
+ dependsOn: loaded.deployment.dependsOn ?? []
116
+ });
117
+ } catch (err) {
118
+ opts.onError?.({
119
+ configPath,
120
+ appDir,
121
+ error: err instanceof Error ? err : new Error(String(err))
122
+ });
123
+ }
124
+ }
125
+
126
+ return results;
127
+ }
@@ -0,0 +1,179 @@
1
+ import { resolve, isAbsolute, join, dirname } from 'node:path';
2
+ import { existsSync, statSync } from 'node:fs';
3
+ import { spawn } from 'node:child_process';
4
+ import type { AppDeployment } from '@vibesdotdev/infra-core/deployment';
5
+
6
+ export interface LoadedDeployment {
7
+ configPath: string;
8
+ appDir: string;
9
+ deployment: AppDeployment;
10
+ provider: string;
11
+ raw: unknown;
12
+ /**
13
+ * Canonical wrangler.jsonc render output, when the default export is a
14
+ * managed Cloudflare web-app deployment (see
15
+ * `@vibesdotdev/infra-cloudflare/regen`). `null` when the export isn't
16
+ * a managed deployment — used by the regen orchestrator to gate writes
17
+ * and the CI drift check.
18
+ */
19
+ wranglerJsoncString: string | null;
20
+ /** Mirrors `webAppDeployment.managed`; `false` when not a managed export. */
21
+ managed: boolean;
22
+ /** Cloudflare Workers script name, when emitted by the managed export. */
23
+ workerName: string | null;
24
+ }
25
+
26
+ const CONFIG_FILENAMES = ['deployment.config.ts', 'deployment.config.js', 'deployment.config.mjs'];
27
+
28
+ export function resolveAppDir(input: string): string {
29
+ const absolute = isAbsolute(input) ? input : resolve(process.cwd(), input);
30
+ if (!existsSync(absolute)) {
31
+ throw new Error(`Path does not exist: ${absolute}`);
32
+ }
33
+ const stat = statSync(absolute);
34
+ if (!stat.isDirectory()) {
35
+ throw new Error(`Expected directory, got file: ${absolute}`);
36
+ }
37
+ return absolute;
38
+ }
39
+
40
+ export function findConfigFile(appDir: string): string {
41
+ for (const name of CONFIG_FILENAMES) {
42
+ const candidate = join(appDir, name);
43
+ if (existsSync(candidate)) return candidate;
44
+ }
45
+ throw new Error(
46
+ `No deployment config found in ${appDir}. Expected one of: ${CONFIG_FILENAMES.join(', ')}`
47
+ );
48
+ }
49
+
50
+ function extractDeployment(raw: unknown): { deployment: AppDeployment; provider: string } {
51
+ if (!raw || typeof raw !== 'object') {
52
+ throw new Error('deployment.config default export must be an object');
53
+ }
54
+ const record = raw as Record<string, unknown>;
55
+ const deployment = (record.deployment ?? record) as AppDeployment | undefined;
56
+ if (!deployment || typeof deployment !== 'object' || !('appId' in deployment)) {
57
+ throw new Error('deployment.config must export an AppDeployment (or { deployment })');
58
+ }
59
+ const provider = (deployment as AppDeployment).provider;
60
+ if (!provider || typeof provider !== 'string') {
61
+ throw new Error('deployment.provider is required');
62
+ }
63
+ return { deployment: deployment as AppDeployment, provider };
64
+ }
65
+
66
+ /**
67
+ * Walk upward from `from` looking for the workspace root (the directory
68
+ * with a `bun.lock` or `package.json` containing `workspaces`).
69
+ */
70
+ function findWorkspaceRoot(from: string): string {
71
+ let dir = from;
72
+ while (dir !== dirname(dir)) {
73
+ if (existsSync(join(dir, 'bun.lock'))) return dir;
74
+ const pkgPath = join(dir, 'package.json');
75
+ if (existsSync(pkgPath)) {
76
+ try {
77
+ const pkg = JSON.parse(require('node:fs').readFileSync(pkgPath, 'utf8'));
78
+ if (pkg.workspaces) return dir;
79
+ } catch {
80
+ // not a parseable package.json; keep walking
81
+ }
82
+ }
83
+ dir = dirname(dir);
84
+ }
85
+ return from;
86
+ }
87
+
88
+ interface SubprocessOutput {
89
+ raw: unknown;
90
+ render: {
91
+ managed: boolean;
92
+ rendered: string | null;
93
+ workerName: string | null;
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Load a deployment.config.ts via a `bun` subprocess from the workspace
99
+ * root. Direct `await import(configPath)` doesn't work when the compiled
100
+ * `vibes` binary runs against a workspace symlink — its bundled module
101
+ * resolver has no idea where `@vibesdotdev/infra-core` lives. Subprocess
102
+ * inherits the workspace's bun (path + node_modules), so workspace
103
+ * package imports resolve as if you ran `bun apps/<app>/deployment.config.ts`
104
+ * directly.
105
+ *
106
+ * The subprocess also tries to import `@vibesdotdev/infra-cloudflare/web-app`
107
+ * and call `renderWranglerJsoncFromDefaultExport(raw)` so the canonical
108
+ * wrangler.jsonc string is returned alongside the raw config. This lets
109
+ * the regen orchestrator stay agnostic of provider-specific rendering.
110
+ * If the import fails (SDK consumer without infra-cloudflare in their
111
+ * tree), the render block is reported as `{ managed: false, rendered: null }`.
112
+ */
113
+ async function loadConfigViaSubprocess(
114
+ configPath: string,
115
+ workspaceRoot: string
116
+ ): Promise<SubprocessOutput> {
117
+ return new Promise((resolvePromise, reject) => {
118
+ const script = `
119
+ const mod = await import(${JSON.stringify(configPath)});
120
+ const raw = mod.default ?? mod;
121
+ let render = { managed: false, rendered: null, workerName: null };
122
+ try {
123
+ const cf = await import('@vibesdotdev/infra-cloudflare/web-app');
124
+ if (typeof cf.renderWranglerJsoncFromDefaultExport === 'function') {
125
+ render = cf.renderWranglerJsoncFromDefaultExport(raw);
126
+ }
127
+ } catch (_) {
128
+ // infra-cloudflare not resolvable; leave render as the default null block.
129
+ }
130
+ process.stdout.write(JSON.stringify({ raw, render }));
131
+ `;
132
+ const child = spawn('bun', ['--bun', '-e', script], {
133
+ cwd: workspaceRoot,
134
+ stdio: ['ignore', 'pipe', 'pipe']
135
+ });
136
+ let stdout = '';
137
+ let stderr = '';
138
+ child.stdout?.on('data', (chunk) => (stdout += String(chunk)));
139
+ child.stderr?.on('data', (chunk) => (stderr += String(chunk)));
140
+ child.on('error', reject);
141
+ child.on('close', (code) => {
142
+ if (code !== 0) {
143
+ reject(
144
+ new Error(
145
+ `bun subprocess exited with code ${code}\nstderr: ${stderr.trim()}\nstdout: ${stdout.trim()}`
146
+ )
147
+ );
148
+ return;
149
+ }
150
+ try {
151
+ resolvePromise(JSON.parse(stdout) as SubprocessOutput);
152
+ } catch (err) {
153
+ reject(
154
+ new Error(
155
+ `Failed to parse subprocess JSON output: ${err instanceof Error ? err.message : String(err)}\nstdout: ${stdout.slice(0, 500)}`
156
+ )
157
+ );
158
+ }
159
+ });
160
+ });
161
+ }
162
+
163
+ export async function loadDeploymentConfig(appDir: string): Promise<LoadedDeployment> {
164
+ const dir = resolveAppDir(appDir);
165
+ const configPath = findConfigFile(dir);
166
+ const workspaceRoot = findWorkspaceRoot(dir);
167
+ const output = await loadConfigViaSubprocess(configPath, workspaceRoot);
168
+ const { deployment, provider } = extractDeployment(output.raw);
169
+ return {
170
+ configPath,
171
+ appDir: dir,
172
+ deployment,
173
+ provider,
174
+ raw: output.raw,
175
+ wranglerJsoncString: output.render.rendered,
176
+ managed: output.render.managed,
177
+ workerName: output.render.workerName
178
+ };
179
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { default as infraDeployPlugin } from './infra-deploy.plugin';
2
+ export { loadDeploymentConfig } from './config-loader.ts';
3
+ export type { LoadedDeployment } from './config-loader.ts';
4
+ export {
5
+ regenerateApp,
6
+ regenerateAll,
7
+ regenExitCode,
8
+ type RegenResult,
9
+ type RegenStatus,
10
+ type RegenerateOptions
11
+ } from './regenerate.ts';