vibecarbon 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/carbon/.env.example +3 -2
- package/carbon/DEVELOPMENT.md +18 -3
- package/carbon/PRODUCTION.md +1 -1
- package/carbon/content/docs/cli.mdx +3 -3
- package/carbon/content/docs/deployment.mdx +1 -1
- package/carbon/content/docs/development.mdx +1 -1
- package/carbon/content/docs/getting-started.mdx +0 -1
- package/carbon/docker-compose.prod.yml +11 -1
- package/carbon/scripts/dev-init.js +1 -1
- package/carbon/scripts/generate-dev-configs.sh +5 -7
- package/carbon/scripts/validate-dev-configs.sh +5 -7
- package/carbon/src/client/components/TechStackSection.tsx +0 -68
- package/carbon/src/client/index.css +0 -14
- package/carbon/src/client/locales/de.json +0 -18
- package/carbon/src/client/locales/en.json +86 -36
- package/carbon/src/client/locales/es.json +0 -18
- package/carbon/src/client/locales/fr.json +0 -18
- package/carbon/src/client/locales/pt.json +0 -18
- package/carbon/src/client/pages/Home.tsx +0 -3
- package/package.json +4 -2
- package/services/index.json +29 -0
- package/services/metabase/compose/docker-compose.override.yml +14 -0
- package/services/metabase/compose/docker-compose.prod.yml +29 -0
- package/services/metabase/compose/docker-compose.yml +68 -0
- package/services/metabase/db/init.sh +29 -0
- package/services/metabase/docs/integration.md +191 -0
- package/services/metabase/k8s/deployment.yaml +88 -0
- package/services/metabase/k8s/ingressroute.yaml +22 -0
- package/services/metabase/k8s/kustomization.yaml +9 -0
- package/services/metabase/k8s/network-policy.yaml +74 -0
- package/services/metabase/k8s/pvc.yaml +12 -0
- package/services/metabase/k8s/service.yaml +15 -0
- package/services/metabase/manifest.json +34 -0
- package/services/metabase/scripts/setup.sh +152 -0
- package/services/n8n/compose/docker-compose.override.yml +14 -0
- package/services/n8n/compose/docker-compose.prod.yml +32 -0
- package/services/n8n/compose/docker-compose.yml +99 -0
- package/services/n8n/db/init.sh +25 -0
- package/services/n8n/docs/SSO.md +124 -0
- package/services/n8n/docs/integration.md +155 -0
- package/services/n8n/hooks/hooks.js +131 -0
- package/services/n8n/k8s/deployment.yaml +116 -0
- package/services/n8n/k8s/ingressroute.yaml +24 -0
- package/services/n8n/k8s/kustomization.yaml +9 -0
- package/services/n8n/k8s/network-policy.yaml +82 -0
- package/services/n8n/k8s/pvc.yaml +14 -0
- package/services/n8n/k8s/service.yaml +14 -0
- package/services/n8n/manifest.json +36 -0
- package/services/n8n/scripts/setup.sh +42 -0
- package/{carbon/docker-compose.observability.prod.yml → services/observability/compose/docker-compose.prod.yml} +8 -0
- package/services/observability/docs/integration.md +31 -0
- package/services/observability/k8s/grafana-deployment.yaml +107 -0
- package/services/observability/k8s/grafana-provisioning-configmap.yaml +65 -0
- package/services/observability/k8s/grafana-pvc.yaml +14 -0
- package/services/observability/k8s/grafana-service.yaml +16 -0
- package/services/observability/k8s/ingressroute.yaml +22 -0
- package/services/observability/k8s/kustomization.yaml +24 -0
- package/services/observability/k8s/loki-configmap.yaml +74 -0
- package/services/observability/k8s/loki-deployment.yaml +72 -0
- package/services/observability/k8s/loki-pvc.yaml +14 -0
- package/services/observability/k8s/loki-service.yaml +16 -0
- package/services/observability/k8s/network-policy.yaml +141 -0
- package/services/observability/k8s/prometheus-configmap.yaml +80 -0
- package/services/observability/k8s/prometheus-deployment.yaml +78 -0
- package/services/observability/k8s/prometheus-pvc.yaml +14 -0
- package/services/observability/k8s/prometheus-rbac.yaml +55 -0
- package/services/observability/k8s/prometheus-service.yaml +16 -0
- package/services/observability/manifest.json +28 -0
- package/services/redis/compose/docker-compose.prod.yml +10 -0
- package/services/redis/compose/docker-compose.yml +24 -0
- package/services/redis/docs/integration.md +148 -0
- package/services/redis/k8s/deployment.yaml +70 -0
- package/services/redis/k8s/kustomization.yaml +8 -0
- package/services/redis/k8s/network-policy.yaml +20 -0
- package/services/redis/k8s/pvc.yaml +12 -0
- package/services/redis/k8s/service.yaml +15 -0
- package/services/redis/manifest.json +30 -0
- package/services/s3/compose/docker-compose.s3-ha.yml +86 -0
- package/services/s3/compose/docker-compose.s3.yml +35 -0
- package/services/s3/manifest.json +87 -0
- package/src/add.js +57 -89
- package/src/cli.js +1 -1
- package/src/create.js +3 -87
- package/src/lib/deploy/bundle.js +4 -2
- package/src/lib/deploy/compose/index.js +45 -17
- package/src/lib/deploy/orchestrator.js +13 -1
- package/src/remove.js +9 -6
- package/carbon/content/docs/optional-services.mdx +0 -160
- package/carbon/src/client/components/MetricsStrip.tsx +0 -48
- /package/{carbon/docker-compose.observability.override.yml → services/observability/compose/docker-compose.override.yml} +0 -0
- /package/{carbon/docker-compose.observability.yml → services/observability/compose/docker-compose.yml} +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards/logs.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards/overview.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards/postgresql.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards.dev/logs.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards.dev/overview.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/dashboards.dev/postgresql.json +0 -0
- /package/{carbon → services/observability}/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +0 -0
- /package/{carbon → services/observability}/volumes/grafana/provisioning/dashboards/dashboards.yml +0 -0
- /package/{carbon → services/observability}/volumes/grafana/provisioning/datasources/datasources.yml +0 -0
- /package/{carbon → services/observability}/volumes/loki/loki-config.yml +0 -0
- /package/{carbon → services/observability}/volumes/prometheus/prometheus.yml +0 -0
- /package/{carbon → services/observability}/volumes/promtail/promtail-config.yml +0 -0
package/src/add.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vibecarbon Add Command
|
|
3
3
|
*
|
|
4
|
-
* Adds optional services to an existing Vibecarbon project.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Adds optional services to an existing Vibecarbon project. Service
|
|
5
|
+
* bundles ship with the CLI and are installed from the packaged
|
|
6
|
+
* `services/` directory by default — the same way `create` scaffolds
|
|
7
|
+
* from the packaged `carbon/` template, so an `add` is version-pinned
|
|
8
|
+
* and works offline. `-online` opts into fetching the latest bundles
|
|
9
|
+
* from GitHub (single-file assets only; directory assets such as `k8s/`
|
|
10
|
+
* still come from the packaged copy).
|
|
8
11
|
*
|
|
9
12
|
* Interactive-by-default: bare `vibecarbon add` prompts for a feature
|
|
10
13
|
* to add. Positional features (`vibecarbon add observability redis`)
|
|
@@ -53,7 +56,9 @@ const GITHUB_BRANCH = 'main';
|
|
|
53
56
|
const GITHUB_RAW_BASE = `https://raw.githubusercontent.com/${GITHUB_REPO}/${GITHUB_BRANCH}`;
|
|
54
57
|
const SERVICES_PATH = 'services';
|
|
55
58
|
|
|
56
|
-
//
|
|
59
|
+
// Packaged services directory — the default install source (shipped in the
|
|
60
|
+
// npm package `files`, alongside `carbon/`). `-online` overrides this to fetch
|
|
61
|
+
// from GitHub.
|
|
57
62
|
const LOCAL_SERVICES_DIR = join(__dirname, '..', SERVICES_PATH);
|
|
58
63
|
|
|
59
64
|
// Template variable placeholders
|
|
@@ -92,7 +97,7 @@ function buildSpec() {
|
|
|
92
97
|
summary: 'Add a feature to a Vibecarbon project',
|
|
93
98
|
description: [
|
|
94
99
|
'Available features:',
|
|
95
|
-
...
|
|
100
|
+
...AVAILABLE_FEATURES.map((f) => ` ${f.name.padEnd(16)} ${f.description}`),
|
|
96
101
|
'',
|
|
97
102
|
'External services (CI/CD, Stripe, OAuth, etc.) live in `vibecarbon configure`.',
|
|
98
103
|
].join('\n'),
|
|
@@ -109,9 +114,9 @@ function buildSpec() {
|
|
|
109
114
|
{ name: 'v', boolean: true, description: 'Show version' },
|
|
110
115
|
{ name: 'y', boolean: true, description: 'Skip confirmation prompts' },
|
|
111
116
|
{
|
|
112
|
-
name: '
|
|
117
|
+
name: 'online',
|
|
113
118
|
boolean: true,
|
|
114
|
-
description: '
|
|
119
|
+
description: 'Fetch the latest bundles from GitHub instead of the packaged copy',
|
|
115
120
|
},
|
|
116
121
|
],
|
|
117
122
|
examples: [
|
|
@@ -227,9 +232,13 @@ async function installServiceDirectory(serviceName, srcDir, destDir, variables,
|
|
|
227
232
|
}
|
|
228
233
|
}
|
|
229
234
|
} else {
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
|
|
235
|
+
// Online mode can't enumerate a remote directory (GitHub raw has no
|
|
236
|
+
// listing and we don't hit the contents API). Directory assets such as
|
|
237
|
+
// `k8s/` therefore only install from the packaged copy.
|
|
238
|
+
throw new Error(
|
|
239
|
+
`Cannot fetch directory "${srcDir}" over -online (remote directory listing is not supported). ` +
|
|
240
|
+
'Drop -online to install this feature from the packaged template.',
|
|
241
|
+
);
|
|
233
242
|
}
|
|
234
243
|
}
|
|
235
244
|
|
|
@@ -290,88 +299,27 @@ function removeFromBaseKustomization(kustomizationEntry) {
|
|
|
290
299
|
writeFileSync(kustomizationPath, content);
|
|
291
300
|
}
|
|
292
301
|
|
|
293
|
-
// ============================================================================
|
|
294
|
-
// SPECIALIZED FEATURE HANDLERS
|
|
295
|
-
// ============================================================================
|
|
296
|
-
|
|
297
|
-
// Features with specialized handlers (not manifest-based). CI/CD used to be
|
|
298
|
-
// here too; it moved to `vibecarbon configure` (CI/CD is an external service,
|
|
299
|
-
// not a local container, so it lives with the other configure features).
|
|
300
|
-
const SPECIALIZED_FEATURES = ['observability'];
|
|
301
|
-
|
|
302
|
-
async function handleSpecializedFeature(featureName, options) {
|
|
303
|
-
const { project } = options;
|
|
304
|
-
|
|
305
|
-
switch (featureName) {
|
|
306
|
-
case 'observability': {
|
|
307
|
-
return handleObservabilityFeature(project);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
default:
|
|
311
|
-
throw new Error(`Unknown specialized feature: ${featureName}`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Enable observability stack (Prometheus, Grafana, Loki)
|
|
317
|
-
* Files are already included in the project - this just enables them
|
|
318
|
-
*/
|
|
319
|
-
async function handleObservabilityFeature(_project) {
|
|
320
|
-
const manifest = loadManifest();
|
|
321
|
-
|
|
322
|
-
// Check if already enabled
|
|
323
|
-
if (manifest.services?.observability) {
|
|
324
|
-
p.log.info('Observability is already enabled.');
|
|
325
|
-
return true;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Check that the observability compose file exists
|
|
329
|
-
const observabilityCompose = join(process.cwd(), 'docker-compose.observability.yml');
|
|
330
|
-
if (!existsSync(observabilityCompose)) {
|
|
331
|
-
p.log.error('Observability files not found. Please ensure your project is up to date.');
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const s = p.spinner();
|
|
336
|
-
s.start('Enabling observability stack');
|
|
337
|
-
|
|
338
|
-
// Set env flags for dashboard visibility (client + server)
|
|
339
|
-
setEnvVar('VITE_OBSERVABILITY_ENABLED', 'true');
|
|
340
|
-
setEnvVar('OBSERVABILITY_ENABLED', 'true');
|
|
341
|
-
|
|
342
|
-
// Update manifest
|
|
343
|
-
if (!manifest.services) manifest.services = {};
|
|
344
|
-
manifest.services.observability = { addedAt: new Date().toISOString() };
|
|
345
|
-
saveManifest(manifest);
|
|
346
|
-
|
|
347
|
-
s.stop('Observability enabled');
|
|
348
|
-
|
|
349
|
-
p.note(
|
|
350
|
-
[
|
|
351
|
-
'Observability stack enabled:',
|
|
352
|
-
` ${c.info('Prometheus')} http://prometheus.localhost`,
|
|
353
|
-
` ${c.info('Grafana')} http://grafana.localhost`,
|
|
354
|
-
` ${c.info('Loki')} http://loki.localhost`,
|
|
355
|
-
].join('\n'),
|
|
356
|
-
'Observability',
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
302
|
// ============================================================================
|
|
363
303
|
// ADD SERVICE
|
|
364
304
|
// ============================================================================
|
|
365
305
|
|
|
366
306
|
async function addService(serviceName, options) {
|
|
367
307
|
const { project } = options;
|
|
368
|
-
let { offline } = options;
|
|
369
308
|
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
309
|
+
// Parked features ship in `services/` but are turned off for this release.
|
|
310
|
+
// Refuse here — before any manifest fetch — so `add <parked>` can't install
|
|
311
|
+
// something we've hidden from the offering. `remove` is intentionally not
|
|
312
|
+
// guarded, so an existing install can still be cleaned up.
|
|
313
|
+
if (isParked(serviceName)) {
|
|
314
|
+
p.log.warn(`"${serviceName}" is not available in this release.`);
|
|
315
|
+
return false;
|
|
373
316
|
}
|
|
374
317
|
|
|
318
|
+
// Default install source is the packaged `services/` dir (mirrors how
|
|
319
|
+
// `create` uses the packaged `carbon/` template). `-online` opts into the
|
|
320
|
+
// GitHub fetch; on network failure we fall back to the packaged copy below.
|
|
321
|
+
let offline = !options.online;
|
|
322
|
+
|
|
375
323
|
let manifest;
|
|
376
324
|
try {
|
|
377
325
|
manifest = await fetchServiceManifest(serviceName, offline);
|
|
@@ -528,11 +476,18 @@ async function addService(serviceName, options) {
|
|
|
528
476
|
// LIST SERVICES
|
|
529
477
|
// ============================================================================
|
|
530
478
|
|
|
531
|
-
// Features that work locally without external dependencies
|
|
479
|
+
// Features that work locally without external dependencies.
|
|
480
|
+
//
|
|
481
|
+
// `status: 'parked'` turns a feature off for this release: it's hidden from the
|
|
482
|
+
// offering (help body + interactive prompt) and `add` refuses to install it,
|
|
483
|
+
// but its packaged bundle under `services/` stays in place so un-parking is a
|
|
484
|
+
// one-line flip. Existing installs can still be `vibecarbon remove`d. Broad
|
|
485
|
+
// add-on discovery will move to a marketplace later — this list is not the
|
|
486
|
+
// permanent catalog.
|
|
532
487
|
const LOCAL_FEATURES = [
|
|
533
488
|
{ name: 'observability', description: 'Prometheus, Grafana, Loki monitoring stack' },
|
|
534
|
-
{ name: 'n8n', description: 'Workflow automation platform' },
|
|
535
|
-
{ name: 'metabase', description: 'Business intelligence and analytics' },
|
|
489
|
+
{ name: 'n8n', description: 'Workflow automation platform', status: 'parked' },
|
|
490
|
+
{ name: 'metabase', description: 'Business intelligence and analytics', status: 'parked' },
|
|
536
491
|
{ name: 'redis', description: 'In-memory cache and session store' },
|
|
537
492
|
];
|
|
538
493
|
|
|
@@ -542,6 +497,19 @@ const LOCAL_FEATURES = [
|
|
|
542
497
|
|
|
543
498
|
const ALL_FEATURES = [...LOCAL_FEATURES];
|
|
544
499
|
|
|
500
|
+
// Features actually offered to users. Parked features are excluded from the
|
|
501
|
+
// help body and the interactive prompt, but remain in ALL_FEATURES so typo
|
|
502
|
+
// suggestions and the parked guard can still resolve their names.
|
|
503
|
+
const AVAILABLE_FEATURES = LOCAL_FEATURES.filter((f) => f.status !== 'parked');
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Whether a feature ships but is turned off for this release.
|
|
507
|
+
* @param {string} name
|
|
508
|
+
*/
|
|
509
|
+
function isParked(name) {
|
|
510
|
+
return LOCAL_FEATURES.some((f) => f.name === name && f.status === 'parked');
|
|
511
|
+
}
|
|
512
|
+
|
|
545
513
|
/**
|
|
546
514
|
* Calculate Levenshtein distance between two strings
|
|
547
515
|
*/
|
|
@@ -652,7 +620,7 @@ async function main(cliArgs) {
|
|
|
652
620
|
if (services.length === 0) {
|
|
653
621
|
const choice = await p.select({
|
|
654
622
|
message: 'Which feature do you want to add?',
|
|
655
|
-
options:
|
|
623
|
+
options: AVAILABLE_FEATURES.map((f) => ({
|
|
656
624
|
value: f.name,
|
|
657
625
|
label: f.name,
|
|
658
626
|
hint: f.description,
|
|
@@ -668,7 +636,7 @@ async function main(cliArgs) {
|
|
|
668
636
|
let success = true;
|
|
669
637
|
for (const serviceName of services) {
|
|
670
638
|
const result = await addService(serviceName, {
|
|
671
|
-
|
|
639
|
+
online: !!values.online,
|
|
672
640
|
project,
|
|
673
641
|
cliArgs: cliArgs, // Pass raw CLI args for infrastructure services
|
|
674
642
|
});
|
package/src/cli.js
CHANGED
|
@@ -95,7 +95,7 @@ ${c.bold('DEV COMMANDS')}
|
|
|
95
95
|
${c.info('down')} Stop local development environment
|
|
96
96
|
${c.info('status')} Show project and deployment status
|
|
97
97
|
${c.info('reset')} Reset local environment (removes all data)
|
|
98
|
-
${c.info('add')} [feature] Add features (
|
|
98
|
+
${c.info('add')} [feature] Add features (observability, redis)
|
|
99
99
|
${c.info('remove')} [feature] Remove features from a project
|
|
100
100
|
${c.info('configure')} Configure external services (billing, OAuth, SMTP, etc.)
|
|
101
101
|
${c.info('upgrade')} Upgrade infrastructure files to latest template
|
package/src/create.js
CHANGED
|
@@ -188,8 +188,8 @@ const SPEC = {
|
|
|
188
188
|
description: 'non-interactive (CI/CD)',
|
|
189
189
|
},
|
|
190
190
|
{
|
|
191
|
-
command: 'vibecarbon create my-saas -
|
|
192
|
-
description: 'create +
|
|
191
|
+
command: 'vibecarbon create my-saas -install',
|
|
192
|
+
description: 'create + run pm install (git is initialized automatically)',
|
|
193
193
|
},
|
|
194
194
|
],
|
|
195
195
|
};
|
|
@@ -697,13 +697,6 @@ async function bootstrap(cliArgs) {
|
|
|
697
697
|
'volumes/kong',
|
|
698
698
|
'volumes/db',
|
|
699
699
|
'volumes/traefik',
|
|
700
|
-
'volumes/prometheus',
|
|
701
|
-
'volumes/loki',
|
|
702
|
-
'volumes/promtail',
|
|
703
|
-
'volumes/grafana/provisioning/dashboards',
|
|
704
|
-
'volumes/grafana/provisioning/datasources',
|
|
705
|
-
'volumes/grafana/dashboards',
|
|
706
|
-
'volumes/grafana/dashboards.dev',
|
|
707
700
|
'.github/workflows',
|
|
708
701
|
'k8s/base/app',
|
|
709
702
|
'k8s/base/backup',
|
|
@@ -758,21 +751,11 @@ async function bootstrap(cliArgs) {
|
|
|
758
751
|
copyTemplate('.dockerignore', join(projectDir, '.dockerignore'), variables);
|
|
759
752
|
copyTemplate('docker-compose.yml', join(projectDir, 'docker-compose.yml'), variables);
|
|
760
753
|
copyTemplate('docker-compose.prod.yml', join(projectDir, 'docker-compose.prod.yml'), variables);
|
|
761
|
-
copyTemplate(
|
|
762
|
-
'docker-compose.observability.yml',
|
|
763
|
-
join(projectDir, 'docker-compose.observability.yml'),
|
|
764
|
-
variables,
|
|
765
|
-
);
|
|
766
754
|
copyTemplate(
|
|
767
755
|
'docker-compose.override.yml',
|
|
768
756
|
join(projectDir, 'docker-compose.override.yml'),
|
|
769
757
|
variables,
|
|
770
758
|
);
|
|
771
|
-
copyTemplate(
|
|
772
|
-
'docker-compose.observability.override.yml',
|
|
773
|
-
join(projectDir, 'docker-compose.observability.override.yml'),
|
|
774
|
-
variables,
|
|
775
|
-
);
|
|
776
759
|
copyTemplate('PRODUCTION.md', join(projectDir, 'PRODUCTION.md'), variables);
|
|
777
760
|
copyTemplate('DEVELOPMENT.md', join(projectDir, 'DEVELOPMENT.md'), variables);
|
|
778
761
|
copyTemplate(
|
|
@@ -780,11 +763,6 @@ async function bootstrap(cliArgs) {
|
|
|
780
763
|
join(projectDir, 'docker-compose.dev-init.yml'),
|
|
781
764
|
variables,
|
|
782
765
|
);
|
|
783
|
-
copyTemplate(
|
|
784
|
-
'docker-compose.observability.prod.yml',
|
|
785
|
-
join(projectDir, 'docker-compose.observability.prod.yml'),
|
|
786
|
-
variables,
|
|
787
|
-
);
|
|
788
766
|
chmodSync(join(projectDir, 'docker-entrypoint.sh'), 0o755);
|
|
789
767
|
|
|
790
768
|
await tick();
|
|
@@ -830,68 +808,6 @@ async function bootstrap(cliArgs) {
|
|
|
830
808
|
variables,
|
|
831
809
|
);
|
|
832
810
|
|
|
833
|
-
// Observability config files (used by deploy --observability)
|
|
834
|
-
copyTemplate(
|
|
835
|
-
'volumes/prometheus/prometheus.yml',
|
|
836
|
-
join(projectDir, 'volumes/prometheus/prometheus.yml'),
|
|
837
|
-
variables,
|
|
838
|
-
);
|
|
839
|
-
copyTemplate(
|
|
840
|
-
'volumes/loki/loki-config.yml',
|
|
841
|
-
join(projectDir, 'volumes/loki/loki-config.yml'),
|
|
842
|
-
variables,
|
|
843
|
-
);
|
|
844
|
-
copyTemplate(
|
|
845
|
-
'volumes/promtail/promtail-config.yml',
|
|
846
|
-
join(projectDir, 'volumes/promtail/promtail-config.yml'),
|
|
847
|
-
variables,
|
|
848
|
-
);
|
|
849
|
-
copyTemplate(
|
|
850
|
-
'volumes/grafana/provisioning/dashboards/dashboards.yml',
|
|
851
|
-
join(projectDir, 'volumes/grafana/provisioning/dashboards/dashboards.yml'),
|
|
852
|
-
variables,
|
|
853
|
-
);
|
|
854
|
-
copyTemplate(
|
|
855
|
-
'volumes/grafana/provisioning/dashboards/dashboards.dev.yml',
|
|
856
|
-
join(projectDir, 'volumes/grafana/provisioning/dashboards/dashboards.dev.yml'),
|
|
857
|
-
variables,
|
|
858
|
-
);
|
|
859
|
-
copyTemplate(
|
|
860
|
-
'volumes/grafana/provisioning/datasources/datasources.yml',
|
|
861
|
-
join(projectDir, 'volumes/grafana/provisioning/datasources/datasources.yml'),
|
|
862
|
-
variables,
|
|
863
|
-
);
|
|
864
|
-
copyTemplate(
|
|
865
|
-
'volumes/grafana/dashboards/logs.json',
|
|
866
|
-
join(projectDir, 'volumes/grafana/dashboards/logs.json'),
|
|
867
|
-
variables,
|
|
868
|
-
);
|
|
869
|
-
copyTemplate(
|
|
870
|
-
'volumes/grafana/dashboards/overview.json',
|
|
871
|
-
join(projectDir, 'volumes/grafana/dashboards/overview.json'),
|
|
872
|
-
variables,
|
|
873
|
-
);
|
|
874
|
-
copyTemplate(
|
|
875
|
-
'volumes/grafana/dashboards/postgresql.json',
|
|
876
|
-
join(projectDir, 'volumes/grafana/dashboards/postgresql.json'),
|
|
877
|
-
variables,
|
|
878
|
-
);
|
|
879
|
-
copyTemplate(
|
|
880
|
-
'volumes/grafana/dashboards.dev/logs.json',
|
|
881
|
-
join(projectDir, 'volumes/grafana/dashboards.dev/logs.json'),
|
|
882
|
-
variables,
|
|
883
|
-
);
|
|
884
|
-
copyTemplate(
|
|
885
|
-
'volumes/grafana/dashboards.dev/overview.json',
|
|
886
|
-
join(projectDir, 'volumes/grafana/dashboards.dev/overview.json'),
|
|
887
|
-
variables,
|
|
888
|
-
);
|
|
889
|
-
copyTemplate(
|
|
890
|
-
'volumes/grafana/dashboards.dev/postgresql.json',
|
|
891
|
-
join(projectDir, 'volumes/grafana/dashboards.dev/postgresql.json'),
|
|
892
|
-
variables,
|
|
893
|
-
);
|
|
894
|
-
|
|
895
811
|
copyTemplate('backup/backup.sh', join(projectDir, 'backup/backup.sh'), variables);
|
|
896
812
|
copyTemplate('backup/compose-backup.sh', join(projectDir, 'backup/compose-backup.sh'), variables);
|
|
897
813
|
copyTemplate('backup/Dockerfile', join(projectDir, 'backup/Dockerfile'), variables);
|
|
@@ -1180,7 +1096,7 @@ fi
|
|
|
1180
1096
|
# Runs lint + unit + component + integration tests.
|
|
1181
1097
|
#
|
|
1182
1098
|
# No-ops gracefully if node_modules isn't present yet — a fresh
|
|
1183
|
-
# \`vibecarbon create
|
|
1099
|
+
# \`vibecarbon create\` lands you on a commit that's pushable
|
|
1184
1100
|
# before you've run \`pnpm install\`. Once deps land the hook gates
|
|
1185
1101
|
# every subsequent push. Bypass at will with \`git push --no-verify\`.
|
|
1186
1102
|
|
package/src/lib/deploy/bundle.js
CHANGED
|
@@ -96,7 +96,9 @@ function composeFileFlags(options = {}) {
|
|
|
96
96
|
if (options.metabase) {
|
|
97
97
|
files.push('-f', 'docker-compose.metabase.yml', '-f', 'docker-compose.metabase.prod.yml');
|
|
98
98
|
}
|
|
99
|
-
if (options.redis)
|
|
99
|
+
if (options.redis) {
|
|
100
|
+
files.push('-f', 'docker-compose.redis.yml', '-f', 'docker-compose.redis.prod.yml');
|
|
101
|
+
}
|
|
100
102
|
return files.join(' ');
|
|
101
103
|
}
|
|
102
104
|
|
|
@@ -126,7 +128,7 @@ export function renderBundle(projectName, options = {}) {
|
|
|
126
128
|
if (options.metabase) {
|
|
127
129
|
rootFiles.push('docker-compose.metabase.yml', 'docker-compose.metabase.prod.yml');
|
|
128
130
|
}
|
|
129
|
-
if (options.redis) rootFiles.push('docker-compose.redis.yml');
|
|
131
|
+
if (options.redis) rootFiles.push('docker-compose.redis.yml', 'docker-compose.redis.prod.yml');
|
|
130
132
|
|
|
131
133
|
for (const file of rootFiles) {
|
|
132
134
|
const src = join(cwd, file);
|
|
@@ -493,7 +493,9 @@ function composeFileFlags(options = {}) {
|
|
|
493
493
|
if (options.metabase) {
|
|
494
494
|
files.push('-f', 'docker-compose.metabase.yml', '-f', 'docker-compose.metabase.prod.yml');
|
|
495
495
|
}
|
|
496
|
-
if (options.redis)
|
|
496
|
+
if (options.redis) {
|
|
497
|
+
files.push('-f', 'docker-compose.redis.yml', '-f', 'docker-compose.redis.prod.yml');
|
|
498
|
+
}
|
|
497
499
|
return files.join(' ');
|
|
498
500
|
}
|
|
499
501
|
|
|
@@ -756,6 +758,27 @@ export async function runMigrations(ip, sshKeyPath, projectName) {
|
|
|
756
758
|
{ timeout: 240_000 },
|
|
757
759
|
);
|
|
758
760
|
|
|
761
|
+
// Also wait for the storage service to finish ITS first-boot migrations
|
|
762
|
+
// before applying app migrations. supabase-storage adds columns to the
|
|
763
|
+
// `storage` schema on startup (notably storage.buckets.public); our
|
|
764
|
+
// 00001_init.sql seeds buckets referencing them. Racing ahead fails with
|
|
765
|
+
// `column "public" of relation "buckets" does not exist`, which aborts the
|
|
766
|
+
// whole migration — the core public.* tables get created first, but the
|
|
767
|
+
// deploy still fails (deployComposeHA only ever masked this by swallowing the
|
|
768
|
+
// error in a try/catch). The supabase_admin gate above is insufficient: that
|
|
769
|
+
// role exists before storage has migrated. Gate on storage.buckets.public
|
|
770
|
+
// being queryable as the proxy for "storage migrations done", polling ~3 min.
|
|
771
|
+
await sshRunAsync(
|
|
772
|
+
ip,
|
|
773
|
+
sshKeyPath,
|
|
774
|
+
`cd ${remoteDir} && ` +
|
|
775
|
+
`for i in $(seq 1 60); do ` +
|
|
776
|
+
`docker compose exec -T db psql -U supabase_admin -d postgres -c 'SELECT public FROM storage.buckets LIMIT 0' >/dev/null 2>&1 && exit 0; ` +
|
|
777
|
+
`echo "[migrate] waiting for storage schema (buckets.public) to be ready (attempt $i/60)"; sleep 3; ` +
|
|
778
|
+
`done; echo "[migrate] storage schema never became ready" >&2; exit 1`,
|
|
779
|
+
{ timeout: 240_000 },
|
|
780
|
+
);
|
|
781
|
+
|
|
759
782
|
// Apply each migration with ON_ERROR_STOP=1 and abort on the first failure.
|
|
760
783
|
// A failed migration MUST fail the deploy — silently shipping an empty schema
|
|
761
784
|
// is far worse than a deploy the operator can see failed and retry. Errors
|
|
@@ -771,6 +794,25 @@ export async function runMigrations(ip, sshKeyPath, projectName) {
|
|
|
771
794
|
`done`,
|
|
772
795
|
{ timeout: 300_000 },
|
|
773
796
|
);
|
|
797
|
+
|
|
798
|
+
// Reload PostgREST schema cache so it sees newly created tables. The old
|
|
799
|
+
// `docker compose exec rest kill -s SIGUSR1 1` never worked — the
|
|
800
|
+
// postgrest/postgrest image has no `kill` binary ("exec: kill: executable
|
|
801
|
+
// file not found in $PATH"), so the reload silently no-op'd and fresh tables
|
|
802
|
+
// stayed invisible (PGRST205) until rest happened to restart. RCA prod-1
|
|
803
|
+
// 2026-05-26. Use the canonical SQL NOTIFY on the pgrst channel instead
|
|
804
|
+
// (db-channel-enabled defaults on); fall back to restarting rest if NOTIFY
|
|
805
|
+
// doesn't land. Best-effort: a missed reload self-heals on the next deploy.
|
|
806
|
+
// Lives here (not just in deployCompose) so every caller — deployCompose,
|
|
807
|
+
// deployComposeHA, and the orchestrator's inline single-compose path — gets
|
|
808
|
+
// a queryable schema instead of PGRST205.
|
|
809
|
+
await sshRunAsync(
|
|
810
|
+
ip,
|
|
811
|
+
sshKeyPath,
|
|
812
|
+
`cd ${remoteDir} && ` +
|
|
813
|
+
`docker compose exec -T db psql -U postgres -d postgres -c "NOTIFY pgrst, 'reload schema'" 2>/dev/null || ` +
|
|
814
|
+
`docker compose restart rest 2>/dev/null || true`,
|
|
815
|
+
);
|
|
774
816
|
}
|
|
775
817
|
|
|
776
818
|
/**
|
|
@@ -837,23 +879,9 @@ export async function deployCompose(options) {
|
|
|
837
879
|
// swallowing it (RCA prod-1 2026-05-26: the previous blanket try/catch hid a
|
|
838
880
|
// fully-failed migration and shipped a schema-less prod that reported success).
|
|
839
881
|
onProgress('Running database migrations...');
|
|
840
|
-
|
|
882
|
+
// runMigrations applies supabase/migrations/* AND reloads PostgREST's schema
|
|
883
|
+
// cache, so the new tables are immediately queryable (no PGRST205).
|
|
841
884
|
await runMigrations(ip, sshKeyPath, projectName);
|
|
842
|
-
// Reload PostgREST schema cache so it sees newly created tables. The old
|
|
843
|
-
// `docker compose exec rest kill -s SIGUSR1 1` never worked — the
|
|
844
|
-
// postgrest/postgrest image has no `kill` binary ("exec: kill: executable
|
|
845
|
-
// file not found in $PATH"), so the reload silently no-op'd and fresh tables
|
|
846
|
-
// stayed invisible (PGRST205) until rest happened to restart. RCA prod-1
|
|
847
|
-
// 2026-05-26. Use the canonical SQL NOTIFY on the pgrst channel instead
|
|
848
|
-
// (db-channel-enabled defaults on); fall back to restarting rest if NOTIFY
|
|
849
|
-
// doesn't land. Best-effort: a missed reload self-heals on the next deploy.
|
|
850
|
-
await sshRunAsync(
|
|
851
|
-
ip,
|
|
852
|
-
sshKeyPath,
|
|
853
|
-
`cd ${remoteDir} && ` +
|
|
854
|
-
`docker compose exec -T db psql -U postgres -d postgres -c "NOTIFY pgrst, 'reload schema'" 2>/dev/null || ` +
|
|
855
|
-
`docker compose restart rest 2>/dev/null || true`,
|
|
856
|
-
);
|
|
857
885
|
|
|
858
886
|
return {
|
|
859
887
|
success: true,
|
|
@@ -542,7 +542,7 @@ export async function executeDeployment(args, gatheredConfig) {
|
|
|
542
542
|
);
|
|
543
543
|
s.stop('Compose HA deployment complete');
|
|
544
544
|
} else {
|
|
545
|
-
const { setupServer, dockerLoginOnServer, startComposeStack } = await import(
|
|
545
|
+
const { setupServer, dockerLoginOnServer, startComposeStack, runMigrations } = await import(
|
|
546
546
|
'./compose/index.js'
|
|
547
547
|
);
|
|
548
548
|
const { setupServerFiles, waitForSSH: waitForComposeSSH } = await import(
|
|
@@ -782,6 +782,18 @@ export async function executeDeployment(args, gatheredConfig) {
|
|
|
782
782
|
startComposeStack(serverIp, sshKeyPath, projectConfig.projectName, services),
|
|
783
783
|
);
|
|
784
784
|
|
|
785
|
+
// Apply app migrations + reload PostgREST. The orchestrator's inline
|
|
786
|
+
// single-compose path previously skipped this (only deployComposeHA ran
|
|
787
|
+
// runMigrations), so compose-single shipped an EMPTY app schema — 0 public
|
|
788
|
+
// tables, every DB-backed feature 500'd, and db_schema verify failed with
|
|
789
|
+
// PGRST205. runMigrations waits for supabase_admin, applies each
|
|
790
|
+
// supabase/migrations/* with ON_ERROR_STOP=1 (a real failure aborts the
|
|
791
|
+
// deploy rather than silently shipping a schema-less prod), then reloads
|
|
792
|
+
// PostgREST's schema cache. Mirrors the HA path.
|
|
793
|
+
await perfAsync('deploy.compose.migrations', () =>
|
|
794
|
+
runMigrations(serverIp, sshKeyPath, projectConfig.projectName),
|
|
795
|
+
);
|
|
796
|
+
|
|
785
797
|
// --- Verifiable success gate ---
|
|
786
798
|
// Probe the app's own /api/health endpoint on the server itself (via
|
|
787
799
|
// localhost + Host header), bypassing DNS/TLS. A successful deploy
|
package/src/remove.js
CHANGED
|
@@ -49,15 +49,15 @@ const SPEC = {
|
|
|
49
49
|
{ name: 'y', boolean: true, description: 'Skip confirmation prompts' },
|
|
50
50
|
{ name: 'force', boolean: true, description: 'Skip confirmation prompt (alias for -y)' },
|
|
51
51
|
{
|
|
52
|
-
name: '
|
|
52
|
+
name: 'online',
|
|
53
53
|
boolean: true,
|
|
54
|
-
description: '
|
|
54
|
+
description: 'Fetch the latest service definitions from GitHub instead of the packaged copy',
|
|
55
55
|
},
|
|
56
56
|
],
|
|
57
57
|
examples: [
|
|
58
58
|
{ command: 'vibecarbon remove', description: 'prompts for a feature to remove' },
|
|
59
|
-
{ command: 'vibecarbon remove
|
|
60
|
-
{ command: 'vibecarbon remove
|
|
59
|
+
{ command: 'vibecarbon remove redis', description: 'remove a specific feature' },
|
|
60
|
+
{ command: 'vibecarbon remove redis -force', description: 'remove without confirmation' },
|
|
61
61
|
],
|
|
62
62
|
};
|
|
63
63
|
|
|
@@ -94,7 +94,10 @@ function removeDirectory(dirPath) {
|
|
|
94
94
|
// ============================================================================
|
|
95
95
|
|
|
96
96
|
async function removeService(serviceName, options) {
|
|
97
|
-
const { force
|
|
97
|
+
const { force } = options;
|
|
98
|
+
// Default to the packaged service definitions (mirrors `add`); `-online`
|
|
99
|
+
// opts into fetching the latest from GitHub.
|
|
100
|
+
const offline = !options.online;
|
|
98
101
|
|
|
99
102
|
const projectManifest = loadManifest();
|
|
100
103
|
|
|
@@ -262,7 +265,7 @@ async function main(cliArgs) {
|
|
|
262
265
|
for (const serviceName of services) {
|
|
263
266
|
const result = await removeService(serviceName, {
|
|
264
267
|
force,
|
|
265
|
-
|
|
268
|
+
online: !!values.online,
|
|
266
269
|
});
|
|
267
270
|
if (!result) {
|
|
268
271
|
success = false;
|