@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 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
- await askConfiguration(out, options);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gambi97/keel-cli",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
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",