@sonarsource/marketing-cli 1.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/README.md +180 -0
- package/dist/branch.d.ts +14 -0
- package/dist/branch.js +27 -0
- package/dist/branches.d.ts +8 -0
- package/dist/branches.js +8 -0
- package/dist/ci.d.ts +25 -0
- package/dist/ci.js +37 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +102 -0
- package/dist/commands/environment.d.ts +36 -0
- package/dist/commands/environment.js +454 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.js +184 -0
- package/dist/commands/migration.d.ts +31 -0
- package/dist/commands/migration.js +111 -0
- package/dist/commands/setup.d.ts +9 -0
- package/dist/commands/setup.js +73 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.js +69 -0
- package/dist/dotenv.d.ts +12 -0
- package/dist/dotenv.js +113 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/kontent.d.ts +8 -0
- package/dist/kontent.js +10 -0
- package/dist/migration.template +9 -0
- package/dist/migrations.d.ts +7 -0
- package/dist/migrations.js +1 -0
- package/dist/netlify.d.ts +49 -0
- package/dist/netlify.js +199 -0
- package/dist/project.d.ts +21 -0
- package/dist/project.js +57 -0
- package/dist/status.d.ts +8 -0
- package/dist/status.js +24 -0
- package/dist/test/handlers.d.ts +18 -0
- package/dist/test/handlers.js +102 -0
- package/dist/test/server.d.ts +1 -0
- package/dist/test/server.js +2 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @sonarsource/marketing-cli
|
|
2
|
+
|
|
3
|
+
CLI tooling for Sonar marketing sites — manages Kontent.ai environments, content-model migrations, and assembles local `.env` files from Netlify.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- [Node.js](https://nodejs.org/) ≥ 22
|
|
8
|
+
- A **Netlify auth token** — run `netlify login` or set `NETLIFY_AUTH_TOKEN` ([docs](https://docs.netlify.com/cli/get-started/#obtain-a-token-in-the-netlify-ui))
|
|
9
|
+
- A **Kontent.ai management API key** — generate one in your project's [API keys settings](https://kontent.ai/learn/docs/apis/openapi/management-api-v2/#section/Authentication)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
yarn add -D @sonarsource/marketing-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
Every consuming repo needs a `marketing.config.json` at its root. If you're migrating from the legacy CLI, `marketing init` generates it for you. Otherwise, create one manually:
|
|
20
|
+
|
|
21
|
+
```jsonc
|
|
22
|
+
{
|
|
23
|
+
"kontent": {
|
|
24
|
+
// Display name shown in Kontent dashboard for the production environment
|
|
25
|
+
"productionEnvironmentName": "PRODUCTION_PROJECT",
|
|
26
|
+
// UUID of the production Kontent environment
|
|
27
|
+
"productionEnvironmentId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
28
|
+
// Role IDs to activate on cloned environments
|
|
29
|
+
"roles": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
|
|
30
|
+
// Optional — declare long-lived branches (epics) and their children
|
|
31
|
+
"longLivedBranches": {},
|
|
32
|
+
},
|
|
33
|
+
"netlify": {
|
|
34
|
+
// Site ID for the preview/branch-deploy Netlify site
|
|
35
|
+
"previewSiteId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
36
|
+
// Site ID for the production Netlify site
|
|
37
|
+
"productionSiteId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
38
|
+
},
|
|
39
|
+
"envVars": {
|
|
40
|
+
// Env var names as they appear on Netlify and in .env files
|
|
41
|
+
"environmentId": "VITE_KONTENT_ENVIRONMENT_ID",
|
|
42
|
+
"secureKey": "KONTENT_SECURE_KEY",
|
|
43
|
+
"previewKey": "KONTENT_PREVIEW_KEY",
|
|
44
|
+
"homepage": "VITE_KONTENT_HOMEPAGE_CODENAME",
|
|
45
|
+
"previewMode": "VITE_KONTENT_PREVIEW_MODE",
|
|
46
|
+
"managementKey": "KONTENT_MANAGEMENT_KEY",
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Workflows
|
|
52
|
+
|
|
53
|
+
### First-time repo onboarding
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
# 1. Clone the repo and install dependencies
|
|
57
|
+
git clone <repo-url> && cd <repo>
|
|
58
|
+
yarn
|
|
59
|
+
|
|
60
|
+
# 2. Set your Netlify token (if not already) in your global profile (zshrc for example)
|
|
61
|
+
export NETLIFY_AUTH_TOKEN=<token>
|
|
62
|
+
|
|
63
|
+
# 3. Assemble .env files from Netlify
|
|
64
|
+
marketing setup
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`setup` fetches env vars from both the production and preview Netlify sites, resolves branch-scoped values when applicable, and writes `.env.development` and `.env.production`. A marker-delimited section inside each file is managed by the CLI — any keys you add outside the markers are left untouched.
|
|
68
|
+
|
|
69
|
+
### Feature branch with content-model changes
|
|
70
|
+
|
|
71
|
+
When your work requires changes to the Kontent.ai content model (adding types, modifying snippets, etc.), clone a dedicated environment so your changes are isolated from production.
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
# 1. Create a branch and clone a Kontent environment for it
|
|
75
|
+
git checkout -b feat/my-feature
|
|
76
|
+
marketing environment create
|
|
77
|
+
|
|
78
|
+
# 2. Scaffold a migration
|
|
79
|
+
marketing migration create add-hero-snippet
|
|
80
|
+
|
|
81
|
+
# 3. Edit the generated file in Migrations/, then run it
|
|
82
|
+
marketing migration run
|
|
83
|
+
|
|
84
|
+
# 4. Develop, iterate, run more migrations as needed
|
|
85
|
+
# ...
|
|
86
|
+
|
|
87
|
+
# 5. When the branch is ready to merge — migrate production and clean up
|
|
88
|
+
marketing environment finish
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`environment create` clones the source Kontent environment, pushes the clone's ID to Netlify as a branch-scoped env var, seeds `Migrations/status.json`, re-runs `setup`, and runs any pending migrations.
|
|
92
|
+
|
|
93
|
+
`environment finish` runs migrations on the source environment, deletes the clone, removes the Netlify branch var, and re-runs `setup`.
|
|
94
|
+
|
|
95
|
+
### Long-lived branches (epics)
|
|
96
|
+
|
|
97
|
+
For work that spans multiple branches (an epic), declare the parent as a long-lived branch so child branches can clone from it instead of production.
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
# 1. Create the epic branch with --stack
|
|
101
|
+
git checkout -b epic/redesign
|
|
102
|
+
marketing environment create --stack
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This clones from production, registers the branch in `longLivedBranches` in `marketing.config.json`, and records the environment ID. Commit this config change — the team needs to see it.
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
# 2. Create child branches — the CLI asks which source to clone from
|
|
109
|
+
git checkout -b feat/new-nav
|
|
110
|
+
marketing environment create
|
|
111
|
+
# ? Select source environment:
|
|
112
|
+
# ❯ Production
|
|
113
|
+
# epic/redesign
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
When you select a long-lived branch as the source, the CLI automatically adds the current branch to that source's `children` in config. No manual config editing is required — branch names are free.
|
|
117
|
+
|
|
118
|
+
You can also skip the selector with `--source`:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
marketing environment create --source epic/redesign
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Children are automatically removed from the parent's `children` on finish. Children must be finished before the parent:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
git checkout feat/new-nav
|
|
128
|
+
marketing environment finish # migrates back to the epic environment
|
|
129
|
+
|
|
130
|
+
git checkout epic/redesign
|
|
131
|
+
marketing environment finish # migrates back to production
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## CI
|
|
135
|
+
|
|
136
|
+
All commands respect `--ci` (or the `CI` environment variable). In CI mode, interactive prompts are skipped — commands that need confirmation require `--yes`. Auth tokens and API keys are resolved from environment variables rather than local dotenv files.
|
|
137
|
+
|
|
138
|
+
## Command reference
|
|
139
|
+
|
|
140
|
+
### Global options
|
|
141
|
+
|
|
142
|
+
| Flag | Description |
|
|
143
|
+
| ------------------ | ----------------------------------------------------------------------------------------- |
|
|
144
|
+
| `--branch <name>` | Override the detected branch (defaults to `git` or `GITHUB_HEAD_REF` / `GITHUB_REF_NAME`) |
|
|
145
|
+
| `--ci` / `--no-ci` | Force CI mode on or off (defaults to the `CI` env var) |
|
|
146
|
+
| `--yes` | Skip confirmation prompts |
|
|
147
|
+
|
|
148
|
+
### `marketing setup`
|
|
149
|
+
|
|
150
|
+
Assemble `.env.development` and `.env.production` from Netlify env vars. Headless — no prompts, safe to run repeatedly.
|
|
151
|
+
|
|
152
|
+
### `marketing init`
|
|
153
|
+
|
|
154
|
+
One-time migration from the legacy CLI. Reads an existing `status.json`, generates `marketing.config.json`, and rewrites `status.json` to the new format.
|
|
155
|
+
|
|
156
|
+
| Flag | Description |
|
|
157
|
+
| ----------- | --------------------------------------------- |
|
|
158
|
+
| `--dry-run` | Print proposed output without writing files |
|
|
159
|
+
| `--force` | Overwrite an existing `marketing.config.json` |
|
|
160
|
+
|
|
161
|
+
### `marketing environment create`
|
|
162
|
+
|
|
163
|
+
Clone a Kontent.ai environment for the current branch.
|
|
164
|
+
|
|
165
|
+
| Flag | Description |
|
|
166
|
+
| ------------------- | ------------------------------------------------------------------------------ |
|
|
167
|
+
| `--stack` | Declare this branch as a long-lived branch in config |
|
|
168
|
+
| `--source <branch>` | Clone from a specific long-lived branch instead of production (skips selector) |
|
|
169
|
+
|
|
170
|
+
### `marketing environment finish`
|
|
171
|
+
|
|
172
|
+
Migrate the source environment, delete the clone, and clean up Netlify vars and status.
|
|
173
|
+
|
|
174
|
+
### `marketing migration create <name>`
|
|
175
|
+
|
|
176
|
+
Scaffold a new timestamp-ordered TypeScript migration in `Migrations/`.
|
|
177
|
+
|
|
178
|
+
### `marketing migration run`
|
|
179
|
+
|
|
180
|
+
Compile and run all pending migrations against the current branch's environment. Refuses to target production directly — use `environment finish` for that.
|
package/dist/branch.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface BranchOptions {
|
|
2
|
+
branch?: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Detect the current branch. Precedence:
|
|
6
|
+
* 1. `--branch <name>` CLI option
|
|
7
|
+
* 2. `GITHUB_HEAD_REF` (PR builds)
|
|
8
|
+
* 3. `GITHUB_REF_NAME` (push builds)
|
|
9
|
+
* 4. `git symbolic-ref --short HEAD`
|
|
10
|
+
*
|
|
11
|
+
* Returns `undefined` on detached HEAD / unresolvable — callers decide how to
|
|
12
|
+
* handle that (e.g. fall back to global/production vars).
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectBranch(opts: BranchOptions): string | undefined;
|
package/dist/branch.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Detect the current branch. Precedence:
|
|
4
|
+
* 1. `--branch <name>` CLI option
|
|
5
|
+
* 2. `GITHUB_HEAD_REF` (PR builds)
|
|
6
|
+
* 3. `GITHUB_REF_NAME` (push builds)
|
|
7
|
+
* 4. `git symbolic-ref --short HEAD`
|
|
8
|
+
*
|
|
9
|
+
* Returns `undefined` on detached HEAD / unresolvable — callers decide how to
|
|
10
|
+
* handle that (e.g. fall back to global/production vars).
|
|
11
|
+
*/
|
|
12
|
+
export function detectBranch(opts) {
|
|
13
|
+
if (opts.branch)
|
|
14
|
+
return opts.branch;
|
|
15
|
+
const headRef = process.env['GITHUB_HEAD_REF'];
|
|
16
|
+
if (headRef)
|
|
17
|
+
return headRef;
|
|
18
|
+
const refName = process.env['GITHUB_REF_NAME'];
|
|
19
|
+
if (refName)
|
|
20
|
+
return refName;
|
|
21
|
+
try {
|
|
22
|
+
return execSync('git symbolic-ref --short HEAD', { encoding: 'utf8' }).trim();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branches that are considered protected (i.e. associated with production).
|
|
3
|
+
* Used by `migration run` and `environment` commands to guard against
|
|
4
|
+
* accidental production mutations.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PROTECTED_BRANCHES: readonly ["master", "main", "develop"];
|
|
7
|
+
/** Hardcoded folder name for Kontent.ai content-model migrations. */
|
|
8
|
+
export declare const MIGRATIONS_FOLDER = "Migrations";
|
package/dist/branches.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branches that are considered protected (i.e. associated with production).
|
|
3
|
+
* Used by `migration run` and `environment` commands to guard against
|
|
4
|
+
* accidental production mutations.
|
|
5
|
+
*/
|
|
6
|
+
export const PROTECTED_BRANCHES = ['master', 'main', 'develop'];
|
|
7
|
+
/** Hardcoded folder name for Kontent.ai content-model migrations. */
|
|
8
|
+
export const MIGRATIONS_FOLDER = 'Migrations';
|
package/dist/ci.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface CIOptions {
|
|
2
|
+
ci?: boolean;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Determine whether we're running in CI. The `--ci`/`--no-ci` flag overrides
|
|
6
|
+
* the environment variable when provided.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isCI(opts: CIOptions): boolean;
|
|
9
|
+
export interface ResolveManagementKeyOptions {
|
|
10
|
+
ci: boolean;
|
|
11
|
+
/** Value fetched from the Netlify preview site, if available */
|
|
12
|
+
netlifyValue: string | undefined;
|
|
13
|
+
/** Path to .env.development for local reads */
|
|
14
|
+
envFilePath: string;
|
|
15
|
+
/** The env var name for the management key (from config) */
|
|
16
|
+
managementKeyName: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the Kontent management API key by precedence:
|
|
20
|
+
* 1. `process.env.KONTENT_MANAGEMENT_KEY` (escape hatch)
|
|
21
|
+
* 2. CI → Netlify preview site value
|
|
22
|
+
* 3. Non-CI → read from local `.env.development`
|
|
23
|
+
* 4. Throw with actionable guidance
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveManagementKey(opts: ResolveManagementKeyOptions): string;
|
package/dist/ci.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readEnvValue } from './dotenv.js';
|
|
2
|
+
import { CliError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Determine whether we're running in CI. The `--ci`/`--no-ci` flag overrides
|
|
5
|
+
* the environment variable when provided.
|
|
6
|
+
*/
|
|
7
|
+
export function isCI(opts) {
|
|
8
|
+
if (opts.ci !== undefined)
|
|
9
|
+
return opts.ci;
|
|
10
|
+
const ci = process.env['CI'];
|
|
11
|
+
return ci !== undefined && ci !== '' && ci !== 'false' && ci !== '0';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the Kontent management API key by precedence:
|
|
15
|
+
* 1. `process.env.KONTENT_MANAGEMENT_KEY` (escape hatch)
|
|
16
|
+
* 2. CI → Netlify preview site value
|
|
17
|
+
* 3. Non-CI → read from local `.env.development`
|
|
18
|
+
* 4. Throw with actionable guidance
|
|
19
|
+
*/
|
|
20
|
+
export function resolveManagementKey(opts) {
|
|
21
|
+
// 1. Explicit env var always wins
|
|
22
|
+
const fromEnv = process.env['KONTENT_MANAGEMENT_KEY'];
|
|
23
|
+
if (fromEnv)
|
|
24
|
+
return fromEnv;
|
|
25
|
+
// 2. CI → Netlify preview site
|
|
26
|
+
if (opts.ci && opts.netlifyValue)
|
|
27
|
+
return opts.netlifyValue;
|
|
28
|
+
// 3. Local .env.development (non-CI, or CI fallback when Netlify value absent)
|
|
29
|
+
const fromFile = readEnvValue(opts.envFilePath, opts.managementKeyName);
|
|
30
|
+
if (fromFile)
|
|
31
|
+
return fromFile;
|
|
32
|
+
// 4. Nothing resolved
|
|
33
|
+
throw new CliError('Could not resolve the Kontent management API key.\n' +
|
|
34
|
+
' • Set KONTENT_MANAGEMENT_KEY in your environment, or\n' +
|
|
35
|
+
` • Add ${opts.managementKeyName}=<key> to ${opts.envFilePath}, or\n` +
|
|
36
|
+
' • Run in CI with the key configured on the Netlify preview site.');
|
|
37
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Build the fully-configured commander program. Kept separate from execution
|
|
4
|
+
* (see index.ts) so tests can assert on the command surface without spawning a
|
|
5
|
+
* process.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildProgram(): Command;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { environmentCreate, environmentFinish } from './commands/environment.js';
|
|
4
|
+
import { init } from './commands/init.js';
|
|
5
|
+
import { migrationCreate, migrationRun } from './commands/migration.js';
|
|
6
|
+
import { setup } from './commands/setup.js';
|
|
7
|
+
import { CliError } from './errors.js';
|
|
8
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
9
|
+
/**
|
|
10
|
+
* Wrap a command action so that {@link CliError} is printed without a stack
|
|
11
|
+
* trace and sets a non-zero exit code, while unexpected errors propagate with
|
|
12
|
+
* their full stack.
|
|
13
|
+
*/
|
|
14
|
+
function wrapAction(fn) {
|
|
15
|
+
return async (...args) => {
|
|
16
|
+
try {
|
|
17
|
+
await fn(...args);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err instanceof CliError) {
|
|
21
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build the fully-configured commander program. Kept separate from execution
|
|
32
|
+
* (see index.ts) so tests can assert on the command surface without spawning a
|
|
33
|
+
* process.
|
|
34
|
+
*/
|
|
35
|
+
export function buildProgram() {
|
|
36
|
+
const program = new Command();
|
|
37
|
+
program
|
|
38
|
+
.name('marketing')
|
|
39
|
+
.description(pkg.description ?? 'Sonar marketing CLI')
|
|
40
|
+
.version(pkg.version)
|
|
41
|
+
.option('--branch <name>', 'Override the detected branch name')
|
|
42
|
+
.option('--ci', 'Force CI mode')
|
|
43
|
+
.option('--no-ci', 'Force non-CI mode')
|
|
44
|
+
.option('--yes', 'Skip confirmation prompts');
|
|
45
|
+
// --- setup (real) ---
|
|
46
|
+
program
|
|
47
|
+
.command('setup')
|
|
48
|
+
.description('Assemble .env.* files from Netlify (headless).')
|
|
49
|
+
.action(wrapAction(async () => {
|
|
50
|
+
const globalOpts = program.opts();
|
|
51
|
+
await setup(globalOpts);
|
|
52
|
+
}));
|
|
53
|
+
// --- init (real) ---
|
|
54
|
+
const initCmd = program
|
|
55
|
+
.command('init')
|
|
56
|
+
.description('Migrate a repo from the legacy CLI to marketing.config.json.')
|
|
57
|
+
.option('--dry-run', 'Print proposed outputs without writing files')
|
|
58
|
+
.option('--force', 'Overwrite existing marketing.config.json');
|
|
59
|
+
initCmd.action(wrapAction(async () => {
|
|
60
|
+
const opts = initCmd.opts();
|
|
61
|
+
await init({ dryRun: opts.dryRun ?? false, force: opts.force ?? false });
|
|
62
|
+
}));
|
|
63
|
+
// --- migration (real) ---
|
|
64
|
+
const migration = program
|
|
65
|
+
.command('migration')
|
|
66
|
+
.description('Create and run Kontent.ai content-model migrations.');
|
|
67
|
+
migration
|
|
68
|
+
.command('create <name>')
|
|
69
|
+
.description('Scaffold a new timestamp-ordered TypeScript migration.')
|
|
70
|
+
.action(wrapAction(async (name) => {
|
|
71
|
+
await migrationCreate(name);
|
|
72
|
+
}));
|
|
73
|
+
migration
|
|
74
|
+
.command('run')
|
|
75
|
+
.description('Compile and run all pending migrations.')
|
|
76
|
+
.action(wrapAction(async () => {
|
|
77
|
+
const globalOpts = program.opts();
|
|
78
|
+
await migrationRun(globalOpts);
|
|
79
|
+
}));
|
|
80
|
+
// --- environment (real) ---
|
|
81
|
+
const environment = program
|
|
82
|
+
.command('environment')
|
|
83
|
+
.description('Create and finish per-branch Kontent.ai environments.');
|
|
84
|
+
const envCreate = environment
|
|
85
|
+
.command('create')
|
|
86
|
+
.description('Clone a Kontent.ai environment for the current branch.')
|
|
87
|
+
.option('--stack', 'Declare this branch as a long-lived branch in config')
|
|
88
|
+
.option('--source <branch>', 'Clone from a specific long-lived branch instead of production');
|
|
89
|
+
envCreate.action(wrapAction(async () => {
|
|
90
|
+
const globalOpts = program.opts();
|
|
91
|
+
const localOpts = envCreate.opts();
|
|
92
|
+
await environmentCreate({ ...globalOpts, stack: localOpts.stack, source: localOpts.source });
|
|
93
|
+
}));
|
|
94
|
+
environment
|
|
95
|
+
.command('finish')
|
|
96
|
+
.description('Migrate source, delete clone, and clean up.')
|
|
97
|
+
.action(wrapAction(async () => {
|
|
98
|
+
const globalOpts = program.opts();
|
|
99
|
+
await environmentFinish(globalOpts);
|
|
100
|
+
}));
|
|
101
|
+
return program;
|
|
102
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type MarketingConfig } from '../config.js';
|
|
2
|
+
export interface EnvironmentCreateOptions {
|
|
3
|
+
branch?: string;
|
|
4
|
+
ci?: boolean;
|
|
5
|
+
yes?: boolean;
|
|
6
|
+
stack?: boolean;
|
|
7
|
+
source?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface EnvironmentFinishOptions {
|
|
10
|
+
branch?: string;
|
|
11
|
+
ci?: boolean;
|
|
12
|
+
yes?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface ResolvedSource {
|
|
15
|
+
environmentId: string;
|
|
16
|
+
isProduction: boolean;
|
|
17
|
+
/** The long-lived branch key this was resolved from, if any. */
|
|
18
|
+
sourceBranch?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Slugify a branch name for use as the Kontent clone name.
|
|
22
|
+
* Non-alphanumeric characters (except `-`) → `-`, consecutive `-` collapsed,
|
|
23
|
+
* leading/trailing `-` trimmed.
|
|
24
|
+
*/
|
|
25
|
+
export declare function slugifyBranch(branch: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Source resolution — deterministic from the `longLivedBranches` config tree.
|
|
28
|
+
*
|
|
29
|
+
* 1. If branch is a child of a key → parent's `environmentId`
|
|
30
|
+
* 2. If branch is a key (not a child of another key) → production
|
|
31
|
+
* 3. Not in config at all → production
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveSourceEnvironment(branch: string, config: MarketingConfig): ResolvedSource;
|
|
34
|
+
export declare function environmentCreate(opts: EnvironmentCreateOptions): Promise<void>;
|
|
35
|
+
export declare function environmentFinish(opts: EnvironmentFinishOptions): Promise<void>;
|
|
36
|
+
export {};
|