@gambi97/keel-cli 0.3.3 → 0.3.4
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/dist/config.js +11 -0
- package/dist/generate.js +22 -0
- package/dist/index.js +12 -2
- package/dist/prompts.js +17 -2
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -46,6 +46,17 @@ export const ENV_PRESETS = {
|
|
|
46
46
|
'dev+staging+prod': ['dev', 'staging', 'prod'],
|
|
47
47
|
};
|
|
48
48
|
export const DEFAULT_ENV_PRESET = 'staging+prod';
|
|
49
|
+
/**
|
|
50
|
+
* Lock a resume's configuration to what the repo was generated with. Scaling
|
|
51
|
+
* is intentionally not carried: it only shapes the tfvars, which are already
|
|
52
|
+
* generated on any run that has a manifest, so post-generate steps never read it.
|
|
53
|
+
*/
|
|
54
|
+
export function hydrateConfigFromManifest(partial, manifest) {
|
|
55
|
+
partial.region = manifest.region;
|
|
56
|
+
partial.environments = manifest.environments;
|
|
57
|
+
partial.objectStorage = manifest.options.objectStorage;
|
|
58
|
+
partial.basicAuth = manifest.options.basicAuth;
|
|
59
|
+
}
|
|
49
60
|
export class ConfigError extends Error {
|
|
50
61
|
}
|
|
51
62
|
const PROJECT_NAME_RE = /^[a-z](?:[a-z0-9-]{0,48}[a-z0-9])?$/;
|
package/dist/generate.js
CHANGED
|
@@ -29,6 +29,28 @@ function renderManifest(answers) {
|
|
|
29
29
|
};
|
|
30
30
|
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Read the committed manifest from a target directory, or undefined when there
|
|
34
|
+
* is none (a fresh run). Used by the resume paths to lock configuration to
|
|
35
|
+
* what the repo was generated with instead of asking for it again.
|
|
36
|
+
*/
|
|
37
|
+
export function readManifest(targetDir) {
|
|
38
|
+
try {
|
|
39
|
+
const raw = readFileSync(join(targetDir, MANIFEST_FILE), 'utf8');
|
|
40
|
+
const m = JSON.parse(raw);
|
|
41
|
+
if (typeof m.projectName === 'string' &&
|
|
42
|
+
typeof m.region === 'string' &&
|
|
43
|
+
Array.isArray(m.environments) &&
|
|
44
|
+
typeof m.options?.objectStorage === 'boolean' &&
|
|
45
|
+
typeof m.options?.basicAuth === 'boolean') {
|
|
46
|
+
return m;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// No manifest or unreadable/invalid: treat as a fresh run.
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
32
54
|
/** Files whose real name would confuse npm packaging are stored renamed. */
|
|
33
55
|
const RENAMES = {
|
|
34
56
|
_gitignore: '.gitignore',
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
|
-
import { ConfigError, finalizeAnswers, fromEnv, mergeAnswers, missingRequired, parseEnvironments, } from './config.js';
|
|
5
|
+
import { ConfigError, finalizeAnswers, fromEnv, hydrateConfigFromManifest, mergeAnswers, missingRequired, parseEnvironments, } from './config.js';
|
|
6
6
|
import { CI_SECRET_NAMES, CI_VARIABLE_NAMES, resourceName } from './contracts.js';
|
|
7
|
-
import { generateProject, GenerateError } from './generate.js';
|
|
7
|
+
import { generateProject, GenerateError, readManifest } from './generate.js';
|
|
8
8
|
import { toolVersion } from './meta.js';
|
|
9
9
|
import { confirmSummary, fillMissing } from './prompts.js';
|
|
10
10
|
import { isDone, loadState, markDone, STATE_FILE, stepData, } from './state.js';
|
|
@@ -333,6 +333,16 @@ async function main() {
|
|
|
333
333
|
collected = await fillMissing(partial, { advanced: flags.advanced, dryRun: flags.dryRun });
|
|
334
334
|
}
|
|
335
335
|
else if (!flags.dryRun) {
|
|
336
|
+
// Resuming non-interactively: lock configuration to the committed manifest
|
|
337
|
+
// so flags/defaults can't silently diverge from the generated repo (e.g. a
|
|
338
|
+
// resume without --object-storage must not flip it off). Same source of
|
|
339
|
+
// truth as the interactive path.
|
|
340
|
+
const resumeDir = partial.targetDir?.trim() || partial.projectName;
|
|
341
|
+
const manifest = resumeDir ? readManifest(resumeDir) : undefined;
|
|
342
|
+
if (manifest && manifest.projectName === partial.projectName) {
|
|
343
|
+
hydrateConfigFromManifest(partial, manifest);
|
|
344
|
+
log.info(`Resuming "${manifest.projectName}" — configuration locked to its .keel manifest.`);
|
|
345
|
+
}
|
|
336
346
|
const missing = missingRequired(partial);
|
|
337
347
|
if (missing.length > 0) {
|
|
338
348
|
cancel(`Non-interactive run is missing required values:\n - ${missing.join('\n - ')}`);
|
package/dist/prompts.js
CHANGED
|
@@ -2,7 +2,8 @@ import * as p from '@clack/prompts';
|
|
|
2
2
|
import { authenticate, createContext, GitHubError, inspectRepo, assertRepoUsable, listOwnedRepos, } from './bootstrap/github.js';
|
|
3
3
|
import { InfisicalError, validateInfisical } from './bootstrap/infisical.js';
|
|
4
4
|
import { ScalewayError, validateScalewayCredentials } from './bootstrap/scaleway.js';
|
|
5
|
-
import { ConfigError, DEFAULT_ENV_PRESET, DEFAULT_INFISICAL_HOST, DEFAULT_REGION, ENV_PRESETS, envDefaultScale, REGIONS, validateProjectName, validateScale, validateUrl, } from './config.js';
|
|
5
|
+
import { ConfigError, DEFAULT_ENV_PRESET, DEFAULT_INFISICAL_HOST, DEFAULT_REGION, ENV_PRESETS, envDefaultScale, hydrateConfigFromManifest, REGIONS, validateProjectName, validateScale, validateUrl, } from './config.js';
|
|
6
|
+
import { readManifest } from './generate.js';
|
|
6
7
|
import { isDone, loadState } from './state.js';
|
|
7
8
|
import { log } from './ui.js';
|
|
8
9
|
function bail() {
|
|
@@ -45,6 +46,17 @@ const text = (message, placeholder) => ask(p.text({
|
|
|
45
46
|
export async function fillMissing(partial, options) {
|
|
46
47
|
const out = structuredClone(partial);
|
|
47
48
|
await askProjectName(out);
|
|
49
|
+
// Resuming an existing project: its configuration is frozen (the repo is
|
|
50
|
+
// already generated with it), so lock it from the committed manifest instead
|
|
51
|
+
// of asking again. Credentials are never persisted, so the provider blocks
|
|
52
|
+
// still run — set SCW_*/INFISICAL_*/GITHUB_TOKEN in the env to skip typing.
|
|
53
|
+
const resumeDir = out.targetDir?.trim() || out.projectName;
|
|
54
|
+
const manifest = readManifest(resumeDir);
|
|
55
|
+
const resuming = manifest !== undefined && manifest.projectName === out.projectName;
|
|
56
|
+
if (manifest && resuming) {
|
|
57
|
+
hydrateConfigFromManifest(out, manifest);
|
|
58
|
+
log.info(`Resuming "${out.projectName}" — region, environments and options are locked to its .keel manifest.`);
|
|
59
|
+
}
|
|
48
60
|
// Each block opens with a "┌ <Provider>" section corner and closes with a
|
|
49
61
|
// "└ <Provider> connected — …" line, so every question flows inside its own
|
|
50
62
|
// section and the next one opens only when the previous is verified.
|
|
@@ -55,7 +67,10 @@ export async function fillMissing(partial, options) {
|
|
|
55
67
|
}
|
|
56
68
|
// Configuration comes last: with the accounts verified, these are the only
|
|
57
69
|
// real choices left. In a dry run it is all that is asked after the name.
|
|
58
|
-
|
|
70
|
+
// On resume it is skipped entirely — the manifest already fixed it.
|
|
71
|
+
if (!resuming) {
|
|
72
|
+
await askConfiguration(out, options);
|
|
73
|
+
}
|
|
59
74
|
return out;
|
|
60
75
|
}
|
|
61
76
|
async function askProjectName(out) {
|
package/package.json
CHANGED