@gambi97/keel-cli 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -207,16 +207,16 @@ Environments are separated with **Terraform workspaces**: same code, one
207
207
  independent state per environment in one bucket, differences confined to the
208
208
  per-environment `<env>.tfvars` files.
209
209
 
210
- | Data | Lives in | Why |
211
- | ----------------------------------- | ------------------------ | ------------------------------------------------------------------------------------ |
212
- | Scaleway API keys | GitHub encrypted secrets | CI needs them to run Terraform |
213
- | Infisical machine identity | GitHub encrypted secrets | Lets Terraform read app secrets at plan/apply |
214
- | Basic Auth user/password | Infisical (non-prod) | App secret, injected into the container, rotatable |
215
- | Database connection string | Infisical (each env) | Complete, ready-to-use value synced by the pipeline after each apply |
216
- | App public URL (`APP_URL`) | Infisical (each env) | Synced by the pipeline once a container exists; for links, OAuth callbacks, webhooks |
217
- | Object Storage coordinates (opt-in) | Infisical (each env) | `S3_*` values synced by the pipeline after each apply |
218
- | Bucket, region, Infisical project | GitHub variables | Non-sensitive wiring, editable in one place |
219
- | Project name, scaling, image | Committed tfvars | Reviewable configuration, no secrets |
210
+ | Data | Lives in | Why |
211
+ | ----------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
212
+ | Scaleway API keys | GitHub encrypted secrets | CI needs them to run Terraform |
213
+ | Infisical machine identity | GitHub encrypted secrets | Lets Terraform read app secrets at plan/apply |
214
+ | Basic Auth user/password | Infisical (non-prod) | App secret, injected into the container, rotatable |
215
+ | Database connection string | Infisical (each env) | Complete, ready-to-use value synced by the pipeline after each apply |
216
+ | App public URL (`APP_URL`) | Infisical (each env) | Synced by the pipeline after each apply (real from the first one — the container starts on keel's placeholder page); for links, OAuth callbacks, webhooks |
217
+ | Object Storage coordinates (opt-in) | Infisical (each env) | `S3_*` values synced by the pipeline after each apply |
218
+ | Bucket, region, Infisical project | GitHub variables | Non-sensitive wiring, editable in one place |
219
+ | Project name, scaling, image | Committed tfvars | Reviewable configuration, no secrets |
220
220
 
221
221
  ## After the bootstrap
222
222
 
package/dist/contracts.js CHANGED
@@ -43,6 +43,21 @@ export const BASE_SYNCED_KEYS = ['DATABASE_URL', 'APP_URL'];
43
43
  */
44
44
  export const BASIC_AUTH_SECRET_KEYS = ['BASIC_AUTH_USER', 'BASIC_AUTH_PASSWORD'];
45
45
  export const BASIC_AUTH_FLAG = 'BASIC_AUTH_ENABLED';
46
+ /**
47
+ * Public image rendered as the default container_image in every generated
48
+ * <env>.tfvars: the very first apply brings a keel-branded page up, so
49
+ * APP_URL is real from day zero. Built and pushed by the Placeholder image
50
+ * workflow from placeholder/ in this repo; it is also the reference
51
+ * implementation of the env contract (PROJECT_NAME, APP_ENVIRONMENT,
52
+ * BASIC_AUTH_*). Users replace it by editing container_image — or set ""
53
+ * to skip the container entirely.
54
+ */
55
+ export const PLACEHOLDER_IMAGE = 'ghcr.io/gambi97/keel-placeholder:v1';
56
+ /**
57
+ * Plain environment variables the generated app_stack injects into the
58
+ * container, so any image (the placeholder first) knows what it runs as.
59
+ */
60
+ export const CONTAINER_ENV_KEYS = ['PROJECT_NAME', 'APP_ENVIRONMENT'];
46
61
  /**
47
62
  * Naming convention for per-environment resources (registry, namespaces,
48
63
  * database, buckets…): mirrored by `local.name` in the app_stack template,
package/dist/generate.js CHANGED
@@ -2,7 +2,7 @@ import { spawnSync } from 'node:child_process';
2
2
  import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { CONTRACT_VERSION } from './contracts.js';
5
+ import { CONTRACT_VERSION, PLACEHOLDER_IMAGE } from './contracts.js';
6
6
  import { toolVersion } from './meta.js';
7
7
  import { STATE_FILE } from './state.js';
8
8
  export class GenerateError extends Error {
@@ -85,6 +85,7 @@ function envTfvarsTokens(answers, env) {
85
85
  __ENABLE_OBJECT_STORAGE__: String(answers.objectStorage),
86
86
  __MIN_SCALE__: String(env.minScale),
87
87
  __MAX_SCALE__: String(env.maxScale),
88
+ __CONTAINER_IMAGE__: PLACEHOLDER_IMAGE,
88
89
  };
89
90
  }
90
91
  /**
package/dist/ui.js CHANGED
@@ -75,14 +75,14 @@ export function renderNextSteps(answers, repoUrl, containerUrlHint) {
75
75
  return [
76
76
  'Next steps:',
77
77
  '',
78
- ` 1. Add a Dockerfile to your application and push its image to the`,
79
- ` registry created by the first pipeline run (${containerUrlHint}).`,
78
+ ` 1. Push to main (or merge a PR) to deploy the non-production environments:`,
79
+ ` ${repoUrl}/actions the first apply already serves keel's placeholder`,
80
+ ` page, so APP_URL is live from day zero.`,
80
81
  ` 2. Replace the placeholder secrets in Infisical (${answers.infisical.host})`,
81
82
  ` project "${answers.infisical.projectName}" with real values.`,
82
- ` 3. Push to main (or merge a PR) to deploy the non-production environments:`,
83
- ` ${repoUrl}/actions`,
84
- ` 4. Set container_image in ${answers.environments.map((e) => `${e.slug}.tfvars`).join(' / ')} once an image exists.`,
85
- ` 5. Release to production with a version tag: git tag v1.0.0 && git push --tags`,
83
+ ` 3. Ship your app: push its image to the registry (${containerUrlHint}),`,
84
+ ` then point container_image at it in ${answers.environments.map((e) => `${e.slug}.tfvars`).join(' / ')}.`,
85
+ ` 4. Release to production with a version tag: git tag v1.0.0 && git push --tags`,
86
86
  '',
87
87
  `Repository: ${repoUrl}`,
88
88
  ].join('\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gambi97/keel-cli",
3
- "version": "0.3.7",
3
+ "version": "0.4.0",
4
4
  "description": "One command from zero to a production-shaped serverless infrastructure on Scaleway (Terraform + GitHub Actions + Infisical). Only Node required.",
5
5
  "keywords": [
6
6
  "keel",
@@ -65,11 +65,13 @@ tag vX.Y.Z -> apply production (the commit the tag points at)
65
65
 
66
66
  ## First deploy
67
67
 
68
- 1. **Merge or push to `main`.** The first pipeline run creates registry and
69
- databases for the non-production environments (and the Object Storage
70
- bucket, if enabled). The Serverless Container is intentionally skipped until
71
- an image exists (`container_image` is empty), so the first apply is green.
72
- Production waits for a version tag (step 5).
68
+ 1. **Merge or push to `main`.** The first pipeline run creates the registry,
69
+ the database and a Serverless Container for each non-production
70
+ environment (and the Object Storage bucket, if enabled). The container
71
+ starts on **keel's placeholder page** (`container_image` in the tfvars), so
72
+ `terraform output container_url` and the `APP_URL` secret — are real from
73
+ day zero. Production waits for a version tag (step 5). Setting
74
+ `container_image = ""` skips the container entirely.
73
75
 
74
76
  2. **Build and push the app image** (any Dockerfile, listening on port 8080
75
77
  by default), replacing `<env>` with your target environment (e.g. staging):
@@ -91,7 +93,7 @@ tag vX.Y.Z -> apply production (the commit the tag points at)
91
93
  | Variable | Where | Notes |
92
94
  |---|---|---|
93
95
  | `DATABASE_URL` | every environment | Complete connection string, auto-updated by the pipeline after each apply. No action needed |
94
- | `APP_URL` | every environment | Public URL of the app, auto-updated by the pipeline once a container exists. Useful for links, OAuth callbacks, webhooks |
96
+ | `APP_URL` | every environment | Public URL of the app, auto-updated by the pipeline after each apply (real from the first one — the placeholder counts). Useful for links, OAuth callbacks, webhooks |
95
97
  | `BASIC_AUTH_USER` / `BASIC_AUTH_PASSWORD` | non-production | The app must enforce these when `BASIC_AUTH_ENABLED=true` |
96
98
  | `S3_BUCKET` / `S3_ENDPOINT` / `S3_REGION` / `S3_ACCESS_KEY` / `S3_SECRET_KEY` | every environment (if Object Storage enabled) | Auto-updated by the pipeline after each apply |
97
99
 
@@ -213,7 +215,11 @@ Avoid local applies: they race against CI on the same state.
213
215
  secret was created with a maximum number of uses and it is spent: create a
214
216
  new client secret with unlimited uses (the pipeline logs in on every
215
217
  plan/apply) and update the `INFISICAL_CLIENT_SECRET` Actions secret.
216
- - **Apply green but no container**: expected until `container_image` is set.
218
+ - **Apply green but no container**: expected only when `container_image` is
219
+ `""`; by default every environment serves keel's placeholder page.
220
+ - **The app answers 401**: non-production environments enforce Basic Auth by
221
+ default — the credentials are `BASIC_AUTH_USER` / `BASIC_AUTH_PASSWORD` in
222
+ that environment's Infisical secrets.
217
223
  - **App can't reach the database**: `DATABASE_URL` is only synced after an
218
224
  apply; check the value in Infisical for that environment. It must contain
219
225
  a username and password (the dedicated IAM credential), not placeholders.
@@ -6,3 +6,8 @@ enable_basic_auth = __ENABLE_BASIC_AUTH__
6
6
  enable_object_storage = __ENABLE_OBJECT_STORAGE__
7
7
  min_scale = __MIN_SCALE__
8
8
  max_scale = __MAX_SCALE__
9
+
10
+ # The image this environment runs. Starts as keel's placeholder page so the
11
+ # first apply brings a container up and APP_URL is real; replace it with your
12
+ # application's image when ready ("" skips the container entirely).
13
+ container_image = "__CONTAINER_IMAGE__"
@@ -81,9 +81,9 @@ resource "scaleway_iam_api_key" "storage" {
81
81
  description = "Object Storage credential for ${local.name} (managed by Terraform)"
82
82
  }
83
83
 
84
- # The container is gated on an image being available: registry and database
85
- # are provisioned first, the container appears once an image has been pushed
86
- # and container_image is set.
84
+ # The container runs whatever image the tfvars point at keel's placeholder
85
+ # page at first, the application once its image is pushed. Setting
86
+ # container_image = "" skips the container entirely.
87
87
  resource "scaleway_container" "this" {
88
88
  count = var.container_image == "" ? 0 : 1
89
89
 
@@ -97,9 +97,15 @@ resource "scaleway_container" "this" {
97
97
  max_scale = var.max_scale
98
98
  privacy = "public"
99
99
 
100
- environment_variables = var.enable_basic_auth ? {
101
- BASIC_AUTH_ENABLED = "true"
102
- } : {}
100
+ # Plain identity variables any image can rely on; the placeholder renders
101
+ # them, real apps are free to ignore them.
102
+ environment_variables = merge(
103
+ {
104
+ PROJECT_NAME = var.project_name
105
+ APP_ENVIRONMENT = var.environment
106
+ },
107
+ var.enable_basic_auth ? { BASIC_AUTH_ENABLED = "true" } : {},
108
+ )
103
109
 
104
110
  # BASIC_AUTH_USER / BASIC_AUTH_PASSWORD / DATABASE_URL arrive here from
105
111
  # Infisical; the app is responsible for enforcing Basic Auth when enabled.