clispark 1.6.1 → 1.7.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
@@ -12,7 +12,7 @@ Interactive scaffolding tool for new CLI projects. Run `npx clispark` to generat
12
12
  npx clispark
13
13
  ```
14
14
 
15
- Answer three prompts — project name, work/private profile, and (for "work") an optional private registry URL — and clispark scaffolds a new directory, running `git init`, `npm install`, and `npm run build` for you. Thirty seconds later:
15
+ Answer four prompts — project name, work/private profile, an optional private registry URL (for "work"), and whether you plan to publish to npm — and clispark scaffolds a new directory, running `git init`, `npm install`, and `npm run build` for you. Thirty seconds later:
16
16
 
17
17
  ```bash
18
18
  cd my-cli
@@ -40,9 +40,10 @@ npx clispark
40
40
 
41
41
  The wizard asks:
42
42
 
43
- 1. **Project name** — checked for availability against the target npm registry as you type; a taken name prompts you to try another instead of blocking hard.
43
+ 1. **Project name**
44
44
  2. **Profile** — `work` or `private`. `work` unlocks an optional registry URL prompt.
45
45
  3. **Registry URL** (work profile only) — leave empty for the public npm registry, or point at a private/company registry. If set, an `.npmrc` is generated so every future `npm install` in the project uses it automatically.
46
+ 4. **Publish to npm?** (default: No) — if yes, the project name is checked for availability against the target npm registry, and a taken name prompts you to try another instead of blocking hard. If no, the check is skipped entirely and the generated `package.json` is marked `"private": true`, so an accidental `npm publish` refuses to run.
46
47
 
47
48
  Scaffolding then happens automatically: files are copied, `git init` plus an initial commit run, and `npm install && npm run build` leave you with a directly runnable project.
48
49
 
package/dist/cli.js CHANGED
@@ -65,22 +65,35 @@ async function runWizard(deps = defaultDeps) {
65
65
  exitIfCancelled(registryValue);
66
66
  registryUrl = registryValue || DEFAULT_REGISTRY_URL;
67
67
  }
68
- let nameAvailability = await deps.checkAvailability(projectName, registryUrl);
69
- while (nameAvailability === "taken") {
70
- log.warn(`"${projectName}" is already taken on ${registryUrl}. Please choose a different name.`);
71
- const retryValue = await text({
72
- message: "Project name",
73
- validate: validateProjectName
74
- });
75
- exitIfCancelled(retryValue);
76
- projectName = retryValue;
68
+ const publishIntentValue = await select({
69
+ message: "Do you plan to publish this to npm?",
70
+ options: [
71
+ { value: false, label: "No" },
72
+ { value: true, label: "Yes" }
73
+ ],
74
+ initialValue: false
75
+ });
76
+ exitIfCancelled(publishIntentValue);
77
+ const publishIntent = publishIntentValue;
78
+ let nameAvailability = "skipped";
79
+ if (publishIntent) {
77
80
  nameAvailability = await deps.checkAvailability(projectName, registryUrl);
78
- }
79
- if (nameAvailability === "unverified") {
80
- log.warn(`Could not verify availability of "${projectName}" on ${registryUrl}. Continuing anyway.`);
81
+ while (nameAvailability === "taken") {
82
+ log.warn(`"${projectName}" is already taken on ${registryUrl}. Please choose a different name.`);
83
+ const retryValue = await text({
84
+ message: "Project name",
85
+ validate: validateProjectName
86
+ });
87
+ exitIfCancelled(retryValue);
88
+ projectName = retryValue;
89
+ nameAvailability = await deps.checkAvailability(projectName, registryUrl);
90
+ }
91
+ if (nameAvailability === "unverified") {
92
+ log.warn(`Could not verify availability of "${projectName}" on ${registryUrl}. Continuing anyway.`);
93
+ }
81
94
  }
82
95
  outro(`Ready to scaffold "${projectName}".`);
83
- return { projectName, profile, registryUrl, nameAvailability };
96
+ return { projectName, profile, registryUrl, publishIntent, nameAvailability };
84
97
  }
85
98
 
86
99
  // src/scaffold.ts
@@ -202,8 +215,14 @@ async function replacePlaceholder(filePath, projectName) {
202
215
  const content = applyPlaceholders(await readFile2(filePath, "utf8"), projectName);
203
216
  await writeFile2(filePath, content);
204
217
  }
218
+ async function markPackageJsonPrivate(targetDir) {
219
+ const packageJsonPath = path2.join(targetDir, "package.json");
220
+ const pkg = JSON.parse(await readFile2(packageJsonPath, "utf8"));
221
+ pkg.private = true;
222
+ await writeFile2(packageJsonPath, JSON.stringify(pkg, null, 2) + "\n");
223
+ }
205
224
  async function copyTemplate(options) {
206
- const { projectName, targetDir, registryUrl } = options;
225
+ const { projectName, targetDir, registryUrl, publishIntent } = options;
207
226
  await assertTargetDirIsUsable(targetDir);
208
227
  await cp(TEMPLATE_DIR, targetDir, { recursive: true });
209
228
  await rename(path2.join(targetDir, "gitignore"), path2.join(targetDir, ".gitignore"));
@@ -211,6 +230,9 @@ async function copyTemplate(options) {
211
230
  await replacePlaceholder(path2.join(targetDir, "README.md"), projectName);
212
231
  await replacePlaceholder(path2.join(targetDir, "src", "logger.ts"), projectName);
213
232
  await replacePlaceholder(path2.join(targetDir, "ARCHITECTURE.md"), projectName);
233
+ if (publishIntent === false) {
234
+ await markPackageJsonPrivate(targetDir);
235
+ }
214
236
  if (registryUrl && registryUrl !== DEFAULT_REGISTRY_URL) {
215
237
  await writeFile2(path2.join(targetDir, ".npmrc"), `registry=${registryUrl}
216
238
  `);
@@ -258,6 +280,19 @@ function getRetentionDays() {
258
280
  const parsed = Number(process.env.LOG_RETENTION_DAYS);
259
281
  return Number.isFinite(parsed) ? parsed : 14;
260
282
  }
283
+ var SENSITIVE_LOG_KEYS = [
284
+ "password",
285
+ "secret",
286
+ "token",
287
+ "apiKey",
288
+ "accessToken",
289
+ "refreshToken",
290
+ "clientSecret",
291
+ "authorization"
292
+ ];
293
+ function buildRedactPaths(keys) {
294
+ return keys.flatMap((key) => [key, `*.${key}`]);
295
+ }
261
296
  function sweepOldLogs(logDir) {
262
297
  try {
263
298
  const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1e3;
@@ -276,7 +311,7 @@ function createLogger(commandName, logDir = paths.log) {
276
311
  const logFilePath = path3.join(logDir, buildLogFileName(commandName));
277
312
  const fileDestination = pino.destination({ dest: logFilePath, sync: true, mode: 384 });
278
313
  const destination = process.env.DEBUG ? pino.multistream([{ stream: fileDestination }, { stream: process.stdout }]) : fileDestination;
279
- const logger = pino({ redact: ["registryUrl", "*.registryUrl"] }, destination);
314
+ const logger = pino({ redact: buildRedactPaths([...SENSITIVE_LOG_KEYS, "registryUrl"]) }, destination);
280
315
  return { logger, logFilePath };
281
316
  }
282
317
  function withLogging(commandName, action, logDir = paths.log, loggerFactory = createLogger) {
@@ -631,7 +666,12 @@ program.action(
631
666
  const answers = await runWizard();
632
667
  const targetDir = path5.join(process.cwd(), answers.projectName);
633
668
  logger.info({ projectName: answers.projectName, targetDir }, "scaffold started");
634
- await scaffoldProject({ projectName: answers.projectName, targetDir, registryUrl: answers.registryUrl });
669
+ await scaffoldProject({
670
+ projectName: answers.projectName,
671
+ targetDir,
672
+ registryUrl: answers.registryUrl,
673
+ publishIntent: answers.publishIntent
674
+ });
635
675
  logger.info({ projectName: answers.projectName }, "scaffold completed");
636
676
  console.log(`
637
677
  Done! Your new CLI project is ready at ${targetDir}`);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/wizard.ts","../src/registry.ts","../src/scaffold.ts","../src/update/manifest.ts","../src/logger.ts","../src/update/update.ts","../src/update/reconcile.ts","../src/update/releasenotes.ts","../src/update/update-package-json.ts"],"sourcesContent":["// src/cli.ts\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { runWizard } from './wizard';\nimport { scaffoldProject } from './scaffold';\nimport { withLogging } from './logger';\nimport { formatUpdateSummary, updateProject } from './update/update';\nimport { fetchReleaseNotes, formatReleaseNotes } from './update/releasenotes';\nimport { getGeneratorVersion } from './update/manifest';\n\nconst program = new Command();\n\nprogram\n .name('clispark')\n .description('Interactive scaffolding tool for new CLI projects')\n .version(getGeneratorVersion());\n\nprogram.action(\n withLogging('scaffold', async (logger) => {\n const answers = await runWizard();\n const targetDir = path.join(process.cwd(), answers.projectName);\n\n logger.info({ projectName: answers.projectName, targetDir }, 'scaffold started');\n await scaffoldProject({ projectName: answers.projectName, targetDir, registryUrl: answers.registryUrl });\n logger.info({ projectName: answers.projectName }, 'scaffold completed');\n\n console.log(`\\nDone! Your new CLI project is ready at ${targetDir}`);\n }),\n);\n\nprogram\n .command('update')\n .description('Update generator-managed core files and dependencies to the latest clispark version')\n .action(\n withLogging('update', async (logger) => {\n const targetDir = process.cwd();\n logger.info({ targetDir }, 'update started');\n const result = await updateProject(targetDir);\n logger.info({ status: result.status }, 'update completed');\n console.log(formatUpdateSummary(result));\n }),\n );\n\nprogram\n .command('releasenotes')\n .description(\"Show what changed between this project's generator version and the latest clispark version\")\n .action(\n withLogging('releasenotes', async (logger) => {\n const targetDir = process.cwd();\n logger.info({ targetDir }, 'releasenotes started');\n const result = await fetchReleaseNotes(targetDir);\n logger.info({ status: result.status }, 'releasenotes completed');\n console.log(formatReleaseNotes(result));\n }),\n );\n\nprogram.parseAsync(process.argv).catch((error: unknown) => {\n console.error(error);\n process.exit(1);\n});\n","// src/wizard.ts\nimport { intro, outro, text, select, log, isCancel, cancel } from '@clack/prompts';\nimport { checkPackageNameAvailability, DEFAULT_REGISTRY_URL } from './registry';\nimport type { Profile, WizardAnswers } from './types';\n\nexport interface WizardDeps {\n checkAvailability: typeof checkPackageNameAvailability;\n}\n\nconst defaultDeps: WizardDeps = {\n checkAvailability: checkPackageNameAvailability,\n};\n\nfunction exitIfCancelled(value: unknown): void {\n if (isCancel(value)) {\n cancel('Operation cancelled.');\n process.exit(1);\n }\n}\n\nfunction validateProjectName(value: string): string | undefined {\n if (!value || value.trim().length === 0) return 'Project name is required.';\n if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {\n return 'Use lowercase letters and numbers, with single hyphens between words (no leading, trailing, or repeated hyphens).';\n }\n return undefined;\n}\n\nexport async function runWizard(deps: WizardDeps = defaultDeps): Promise<WizardAnswers> {\n intro('clispark — scaffold a new CLI project');\n\n const nameValue = await text({\n message: 'Project name',\n validate: validateProjectName,\n });\n exitIfCancelled(nameValue);\n let projectName = nameValue as string;\n\n const profileValue = await select({\n message: 'Is this a work or private project?',\n options: [\n { value: 'private', label: 'Private' },\n { value: 'work', label: 'Work' },\n ],\n });\n exitIfCancelled(profileValue);\n const profile = profileValue as Profile;\n\n let registryUrl = DEFAULT_REGISTRY_URL;\n if (profile === 'work') {\n const registryValue = await text({\n message: 'Custom npm registry URL (leave empty for npmjs.org)',\n placeholder: DEFAULT_REGISTRY_URL,\n defaultValue: DEFAULT_REGISTRY_URL,\n });\n exitIfCancelled(registryValue);\n registryUrl = (registryValue as string) || DEFAULT_REGISTRY_URL;\n }\n\n let nameAvailability = await deps.checkAvailability(projectName, registryUrl);\n\n while (nameAvailability === 'taken') {\n log.warn(`\"${projectName}\" is already taken on ${registryUrl}. Please choose a different name.`);\n\n const retryValue = await text({\n message: 'Project name',\n validate: validateProjectName,\n });\n exitIfCancelled(retryValue);\n projectName = retryValue as string;\n\n nameAvailability = await deps.checkAvailability(projectName, registryUrl);\n }\n\n if (nameAvailability === 'unverified') {\n log.warn(`Could not verify availability of \"${projectName}\" on ${registryUrl}. Continuing anyway.`);\n }\n\n outro(`Ready to scaffold \"${projectName}\".`);\n\n return { projectName, profile, registryUrl, nameAvailability };\n}\n","export const DEFAULT_REGISTRY_URL = 'https://registry.npmjs.org';\n\nexport type NameCheckResult = 'available' | 'taken' | 'unverified';\n\nexport async function checkPackageNameAvailability(\n name: string,\n registryUrl: string = DEFAULT_REGISTRY_URL,\n): Promise<NameCheckResult> {\n const url = `${registryUrl.replace(/\\/$/, '')}/${encodeURIComponent(name)}`;\n\n try {\n const response = await fetch(url);\n if (response.status === 404) return 'available';\n if (response.status === 200) return 'taken';\n return 'unverified';\n } catch {\n return 'unverified';\n }\n}\n","import spawn from 'cross-spawn';\nimport { fileURLToPath } from 'node:url';\nimport path from 'node:path';\nimport { cp, readdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { DEFAULT_REGISTRY_URL } from './registry';\nimport { buildManifest, getGeneratorVersion, writeManifest } from './update/manifest';\n\nexport interface ScaffoldOptions {\n projectName: string;\n targetDir: string;\n registryUrl?: string;\n}\n\nexport const TEMPLATE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'templates', 'base');\n\nasync function assertTargetDirIsUsable(targetDir: string): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(targetDir);\n } catch {\n return;\n }\n if (entries.length > 0) {\n throw new Error(`Directory \"${targetDir}\" already exists and is not empty.`);\n }\n}\n\nexport function applyPlaceholders(content: string, projectName: string): string {\n return content.replaceAll('{{projectName}}', projectName);\n}\n\nasync function replacePlaceholder(filePath: string, projectName: string): Promise<void> {\n const content = applyPlaceholders(await readFile(filePath, 'utf8'), projectName);\n await writeFile(filePath, content);\n}\n\nexport async function copyTemplate(options: ScaffoldOptions): Promise<void> {\n const { projectName, targetDir, registryUrl } = options;\n\n await assertTargetDirIsUsable(targetDir);\n await cp(TEMPLATE_DIR, targetDir, { recursive: true });\n\n await rename(path.join(targetDir, 'gitignore'), path.join(targetDir, '.gitignore'));\n\n await replacePlaceholder(path.join(targetDir, 'package.json'), projectName);\n await replacePlaceholder(path.join(targetDir, 'README.md'), projectName);\n await replacePlaceholder(path.join(targetDir, 'src', 'logger.ts'), projectName);\n await replacePlaceholder(path.join(targetDir, 'ARCHITECTURE.md'), projectName);\n\n if (registryUrl && registryUrl !== DEFAULT_REGISTRY_URL) {\n await writeFile(path.join(targetDir, '.npmrc'), `registry=${registryUrl}\\n`);\n }\n}\n\nexport interface ScaffoldDeps {\n runCommand: (command: string, args: string[], cwd: string) => Promise<void>;\n}\n\nasync function defaultRunCommand(command: string, args: string[], cwd: string): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const child = spawn(command, args, { cwd, stdio: 'inherit' });\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Command \"${command} ${args.join(' ')}\" exited with code ${code}`));\n }\n });\n });\n}\n\nconst defaultScaffoldDeps: ScaffoldDeps = { runCommand: defaultRunCommand };\n\nexport async function scaffoldProject(\n options: ScaffoldOptions,\n deps: ScaffoldDeps = defaultScaffoldDeps,\n): Promise<void> {\n await copyTemplate(options);\n\n const { targetDir } = options;\n const manifest = await buildManifest(targetDir, getGeneratorVersion());\n await writeManifest(targetDir, manifest);\n\n await deps.runCommand('git', ['init'], targetDir);\n await deps.runCommand('git', ['add', '-A'], targetDir);\n await deps.runCommand('git', ['commit', '-m', 'chore: initial scaffold from clispark'], targetDir);\n await deps.runCommand('npm', ['install'], targetDir);\n await deps.runCommand('npm', ['run', 'build'], targetDir);\n}\n","// src/update/manifest.ts\nimport { createHash } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport const CORE_FILE_PATHS = [\n 'bin/run.ts',\n 'src/index.ts',\n 'src/base-command.ts',\n 'src/logger.ts',\n 'tsup.config.ts',\n 'vitest.config.ts',\n 'tsconfig.json',\n 'ARCHITECTURE.md',\n '.gitignore',\n] as const;\n\nexport const CORE_SCRIPT_NAMES = ['build', 'postbuild', 'pretest', 'test', 'typecheck'] as const;\n\n/** The base template stores .gitignore as \"gitignore\" (renamed on copy); every other core path is identical in the template and in a generated project. */\nexport function templateSourcePath(relativePath: string): string {\n return relativePath === '.gitignore' ? 'gitignore' : relativePath;\n}\n\nexport interface Manifest {\n generatorVersion: string;\n coreFiles: Record<string, string>;\n coreDependencies: Record<string, string>;\n coreScripts: Record<string, string>;\n coreFields: {\n engines: Record<string, string>;\n oclif: Record<string, unknown>;\n };\n}\n\nexport interface PackageJsonCore {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n scripts?: Record<string, string>;\n engines?: Record<string, string>;\n oclif?: Record<string, unknown>;\n}\n\nexport function hashContent(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nexport async function hashCoreFiles(dir: string): Promise<Record<string, string>> {\n const hashes: Record<string, string> = {};\n for (const relativePath of CORE_FILE_PATHS) {\n const content = await readFile(path.join(dir, relativePath), 'utf8');\n hashes[relativePath] = hashContent(content);\n }\n return hashes;\n}\n\nexport function extractCoreFields(\n pkg: PackageJsonCore,\n): Pick<Manifest, 'coreDependencies' | 'coreScripts' | 'coreFields'> {\n const coreDependencies = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };\n\n const coreScripts: Record<string, string> = {};\n for (const name of CORE_SCRIPT_NAMES) {\n const value = pkg.scripts?.[name];\n if (value !== undefined) coreScripts[name] = value;\n }\n\n return {\n coreDependencies,\n coreScripts,\n coreFields: {\n engines: pkg.engines ?? {},\n oclif: pkg.oclif ?? {},\n },\n };\n}\n\nexport async function buildManifest(targetDir: string, generatorVersion: string): Promise<Manifest> {\n const coreFiles = await hashCoreFiles(targetDir);\n const pkg = JSON.parse(await readFile(path.join(targetDir, 'package.json'), 'utf8')) as PackageJsonCore;\n const { coreDependencies, coreScripts, coreFields } = extractCoreFields(pkg);\n return { generatorVersion, coreFiles, coreDependencies, coreScripts, coreFields };\n}\n\nexport const MANIFEST_RELATIVE_PATH = path.join('.clispark', 'manifest.json');\n\nexport async function writeManifest(targetDir: string, manifest: Manifest): Promise<void> {\n const manifestPath = path.join(targetDir, MANIFEST_RELATIVE_PATH);\n await mkdir(path.dirname(manifestPath), { recursive: true });\n await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\\n');\n}\n\nexport async function readManifest(targetDir: string): Promise<Manifest | undefined> {\n try {\n const content = await readFile(path.join(targetDir, MANIFEST_RELATIVE_PATH), 'utf8');\n return JSON.parse(content) as Manifest;\n } catch {\n return undefined;\n }\n}\n\nexport async function requireManifest(targetDir: string): Promise<Manifest> {\n const manifest = await readManifest(targetDir);\n if (!manifest) {\n throw new Error(\n 'No .clispark/manifest.json found — this project predates update support, or is not a clispark project.',\n );\n }\n return manifest;\n}\n\n/**\n * Finds clispark's own package.json by walking up from this file's location.\n * A fixed relative path (`../package.json`) can't work here: this module's\n * depth below the package root differs between running from source (tests)\n * and running as part of the bundled `dist/cli.js` (tsup flattens everything\n * into one file, so `import.meta.url` no longer reflects the original\n * per-module nesting).\n */\nexport function getGeneratorVersion(): string {\n let dir = path.dirname(fileURLToPath(import.meta.url));\n while (true) {\n const pkgPath = path.join(dir, 'package.json');\n if (existsSync(pkgPath)) {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string; version: string };\n if (pkg.name === 'clispark') return pkg.version;\n }\n const parentDir = path.dirname(dir);\n if (parentDir === dir) {\n throw new Error(\"Could not locate clispark's own package.json.\");\n }\n dir = parentDir;\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport { mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs';\nimport path from 'node:path';\nimport envPaths from 'env-paths';\nimport pino, { type Logger } from 'pino';\n\nconst paths = envPaths('clispark', { suffix: '' });\n\nexport interface LoggerHandle {\n logger: Logger;\n logFilePath: string;\n}\n\nfunction buildLogFileName(commandName: string): string {\n const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-');\n const suffix = randomBytes(3).toString('hex');\n return `${commandName}-${timestamp}-${suffix}.log`;\n}\n\nfunction getRetentionDays(): number {\n const parsed = Number(process.env.LOG_RETENTION_DAYS);\n return Number.isFinite(parsed) ? parsed : 14;\n}\n\nfunction sweepOldLogs(logDir: string): void {\n try {\n const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1000;\n for (const file of readdirSync(logDir)) {\n const filePath = path.join(logDir, file);\n if (statSync(filePath).mtimeMs < cutoffMs) {\n unlinkSync(filePath);\n }\n }\n } catch {\n // best-effort cleanup; a broken sweep must never block the actual command\n }\n}\n\nexport function createLogger(commandName: string, logDir: string = paths.log): LoggerHandle {\n mkdirSync(logDir, { recursive: true });\n sweepOldLogs(logDir);\n\n const logFilePath = path.join(logDir, buildLogFileName(commandName));\n const fileDestination = pino.destination({ dest: logFilePath, sync: true, mode: 0o600 });\n const destination = process.env.DEBUG\n ? pino.multistream([{ stream: fileDestination }, { stream: process.stdout }])\n : fileDestination;\n const logger = pino({ redact: ['registryUrl', '*.registryUrl'] }, destination);\n\n return { logger, logFilePath };\n}\n\nexport function withLogging(\n commandName: string,\n action: (logger: Logger) => Promise<void>,\n logDir: string = paths.log,\n loggerFactory: typeof createLogger = createLogger,\n): () => Promise<void> {\n return async () => {\n let handle: LoggerHandle;\n try {\n handle = loggerFactory(commandName, logDir);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\\n✖ ${message}`);\n process.exit(1);\n return;\n }\n\n const { logger, logFilePath } = handle;\n try {\n logger.info({ command: commandName }, 'started');\n } catch {\n // best-effort logging; a write failure here must not abort a command that hasn't run yet\n }\n\n try {\n await action(logger);\n try {\n logger.info({ command: commandName }, 'completed');\n } catch {\n // best-effort logging; the command still succeeded\n }\n if (process.env.DEBUG) {\n console.log(`Details: ${logFilePath}`);\n }\n } catch (error) {\n try {\n logger.error({ command: commandName, err: error }, 'failed');\n } catch {\n // best-effort logging; never let a log-write failure mask the real error\n }\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\\n✖ ${message}`);\n console.error(`Details: ${logFilePath}`);\n process.exit(1);\n }\n };\n}\n","// src/update/update.ts\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport spawn from 'cross-spawn';\nimport { applyPlaceholders, TEMPLATE_DIR } from '../scaffold';\nimport {\n CORE_FILE_PATHS,\n getGeneratorVersion,\n hashContent,\n requireManifest,\n templateSourcePath,\n writeManifest,\n type Manifest,\n} from './manifest';\nimport { reconcileEntry, stringEquals } from './reconcile';\nimport { compareVersions } from './releasenotes';\nimport { mergePackageJson, type FieldOutcome, type PackageJsonShape } from './update-package-json';\n\nexport interface UpdateDeps {\n runCommand: (command: string, args: string[], cwd: string) => Promise<void>;\n captureCommand: (command: string, args: string[], cwd: string) => Promise<string>;\n}\n\nasync function defaultRunCommand(command: string, args: string[], cwd: string): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const child = spawn(command, args, { cwd, stdio: 'inherit' });\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) resolve();\n else reject(new Error(`Command \"${command} ${args.join(' ')}\" exited with code ${code}`));\n });\n });\n}\n\nasync function defaultCaptureCommand(command: string, args: string[], cwd: string): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const child = spawn(command, args, { cwd });\n let stdout = '';\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) resolve(stdout);\n else reject(new Error(`Command \"${command} ${args.join(' ')}\" exited with code ${code}`));\n });\n });\n}\n\nconst defaultUpdateDeps: UpdateDeps = { runCommand: defaultRunCommand, captureCommand: defaultCaptureCommand };\n\nexport interface FileOutcomeEntry {\n path: string;\n outcome: 'added' | 'replaced' | 'skipped' | 'no-longer-core';\n}\n\nexport interface UpdateResult {\n status: 'up-to-date' | 'no-changes' | 'updated';\n fromVersion: string;\n toVersion: string;\n files: FileOutcomeEntry[];\n dependencies: FieldOutcome[];\n scripts: FieldOutcome[];\n fields: FieldOutcome[];\n}\n\nexport async function updateProject(targetDir: string, deps: UpdateDeps = defaultUpdateDeps): Promise<UpdateResult> {\n const status = (await deps.captureCommand('git', ['status', '--porcelain'], targetDir)).trim();\n if (status.length > 0) {\n throw new Error('Working tree is not clean. Commit or stash your changes before running update.');\n }\n\n const oldManifest = await requireManifest(targetDir);\n const toVersion = getGeneratorVersion();\n const fromVersion = oldManifest.generatorVersion;\n\n if (compareVersions(fromVersion, toVersion) >= 0) {\n return { status: 'up-to-date', fromVersion, toVersion, files: [], dependencies: [], scripts: [], fields: [] };\n }\n\n const currentPkg = JSON.parse(await readFile(path.join(targetDir, 'package.json'), 'utf8')) as PackageJsonShape;\n const projectName = currentPkg.name;\n\n const newTemplatePkg = JSON.parse(\n applyPlaceholders(await readFile(path.join(TEMPLATE_DIR, 'package.json'), 'utf8'), projectName),\n ) as PackageJsonShape;\n\n const files: FileOutcomeEntry[] = [];\n const newCoreFiles: Record<string, string> = {};\n const fileWrites: { targetPath: string; content: string }[] = [];\n\n for (const relativePath of CORE_FILE_PATHS) {\n const newContent = applyPlaceholders(\n await readFile(path.join(TEMPLATE_DIR, templateSourcePath(relativePath)), 'utf8'),\n projectName,\n );\n const newHash = hashContent(newContent);\n\n let currentHash: string | undefined;\n try {\n currentHash = hashContent(await readFile(path.join(targetDir, relativePath), 'utf8'));\n } catch {\n currentHash = undefined;\n }\n\n const result = reconcileEntry(currentHash, oldManifest.coreFiles[relativePath], newHash, stringEquals);\n files.push({ path: relativePath, outcome: result.outcome });\n newCoreFiles[relativePath] = result.value;\n\n if (result.outcome === 'added' || result.outcome === 'replaced') {\n fileWrites.push({ targetPath: path.join(targetDir, relativePath), content: newContent });\n }\n }\n\n for (const relativePath of Object.keys(oldManifest.coreFiles)) {\n if ((CORE_FILE_PATHS as readonly string[]).includes(relativePath)) continue;\n try {\n await readFile(path.join(targetDir, relativePath), 'utf8');\n files.push({ path: relativePath, outcome: 'no-longer-core' });\n } catch {\n // already gone locally, nothing to report\n }\n }\n\n const pkgMerge = mergePackageJson(currentPkg, oldManifest, newTemplatePkg);\n\n const hasFileChanges = files.some(\n (f) => f.outcome === 'added' || f.outcome === 'replaced' || f.outcome === 'no-longer-core',\n );\n const hasChanges = hasFileChanges || pkgMerge.changed;\n\n if (!hasChanges) {\n return {\n status: 'no-changes',\n fromVersion,\n toVersion,\n files,\n dependencies: pkgMerge.dependencies,\n scripts: pkgMerge.scripts,\n fields: pkgMerge.fields,\n };\n }\n\n for (const write of fileWrites) {\n await mkdir(path.dirname(write.targetPath), { recursive: true });\n await writeFile(write.targetPath, write.content);\n }\n\n if (pkgMerge.changed) {\n await writeFile(path.join(targetDir, 'package.json'), JSON.stringify(pkgMerge.updatedPkg, null, 2) + '\\n');\n }\n\n const newManifest: Manifest = {\n generatorVersion: toVersion,\n coreFiles: newCoreFiles,\n coreDependencies: pkgMerge.coreDependencies,\n coreScripts: pkgMerge.coreScripts,\n coreFields: pkgMerge.coreFields,\n };\n await writeManifest(targetDir, newManifest);\n\n await deps.runCommand('git', ['add', '-A'], targetDir);\n await deps.runCommand('git', ['commit', '-m', `chore: update clispark core to v${toVersion}`], targetDir);\n\n return {\n status: 'updated',\n fromVersion,\n toVersion,\n files,\n dependencies: pkgMerge.dependencies,\n scripts: pkgMerge.scripts,\n fields: pkgMerge.fields,\n };\n}\n\nexport function formatUpdateSummary(result: UpdateResult): string {\n if (result.status === 'up-to-date') {\n return `Already up to date (v${result.toVersion}).`;\n }\n\n const added = result.files.filter((f) => f.outcome === 'added');\n const replaced = result.files.filter((f) => f.outcome === 'replaced');\n const skipped = result.files.filter((f) => f.outcome === 'skipped');\n const noLongerCore = result.files.filter((f) => f.outcome === 'no-longer-core');\n\n const lines: string[] = [];\n if (result.status === 'no-changes') {\n lines.push(\n noLongerCore.length\n ? `No changes applied: nothing to update since v${result.fromVersion}.`\n : `No changes applied: every core file/field has been modified locally since v${result.fromVersion}.`,\n );\n } else {\n lines.push(`Updated core from v${result.fromVersion} to v${result.toVersion}.`);\n }\n\n if (added.length) lines.push(` New: ${added.map((f) => f.path).join(', ')}`);\n if (replaced.length) lines.push(` Updated: ${replaced.map((f) => f.path).join(', ')}`);\n if (skipped.length) lines.push(` Skipped (locally modified): ${skipped.map((f) => f.path).join(', ')}`);\n if (noLongerCore.length) {\n lines.push(\n ` No longer part of the core, safe to remove manually: ${noLongerCore.map((f) => f.path).join(', ')}`,\n );\n }\n\n const fieldOutcomes = [...result.dependencies, ...result.scripts, ...result.fields].filter(\n (o) => o.outcome !== 'skipped',\n );\n if (fieldOutcomes.length) {\n lines.push(` package.json: ${fieldOutcomes.map((o) => `${o.key} (${o.outcome})`).join(', ')}`);\n }\n\n if (result.status === 'updated') {\n lines.push('Run \"clispark releasenotes\" to see what changed.');\n }\n\n return lines.join('\\n');\n}\n","export type ReconcileOutcome = 'added' | 'replaced' | 'skipped';\n\nexport interface ReconcileResult<T> {\n outcome: ReconcileOutcome;\n value: T;\n}\n\nexport function reconcileEntry<T>(\n currentLiveValue: T | undefined,\n oldManifestValue: T | undefined,\n newTemplateValue: T,\n isEqual: (a: T, b: T) => boolean,\n): ReconcileResult<T> {\n if (currentLiveValue === undefined) {\n return { outcome: 'added', value: newTemplateValue };\n }\n if (oldManifestValue !== undefined && isEqual(currentLiveValue, oldManifestValue)) {\n return { outcome: 'replaced', value: newTemplateValue };\n }\n return { outcome: 'skipped', value: oldManifestValue ?? currentLiveValue };\n}\n\nexport function stringEquals(a: string, b: string): boolean {\n return a === b;\n}\n\nexport function deepEquals(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n","// src/update/releasenotes.ts\nimport { getGeneratorVersion, requireManifest } from './manifest';\n\nexport interface GitHubRelease {\n tag_name: string;\n name: string;\n body: string;\n}\n\nfunction stripV(version: string): string {\n return version.startsWith('v') ? version.slice(1) : version;\n}\n\nexport function compareVersions(a: string, b: string): number {\n const partsA = stripV(a).split('.').map(Number);\n const partsB = stripV(b).split('.').map(Number);\n const length = Math.max(partsA.length, partsB.length);\n for (let i = 0; i < length; i++) {\n const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);\n if (diff !== 0) return diff;\n }\n return 0;\n}\n\nexport interface ReleaseNotesResult {\n status: 'up-to-date' | 'releases-found';\n fromVersion: string;\n toVersion: string;\n releases: GitHubRelease[];\n}\n\nconst RELEASES_URL = 'https://api.github.com/repos/martinwichner/clispark/releases';\n\nexport async function fetchReleaseNotes(\n targetDir: string,\n fetchFn: typeof fetch = fetch,\n): Promise<ReleaseNotesResult> {\n const manifest = await requireManifest(targetDir);\n const fromVersion = manifest.generatorVersion;\n const toVersion = getGeneratorVersion();\n\n if (compareVersions(fromVersion, toVersion) >= 0) {\n return { status: 'up-to-date', fromVersion, toVersion, releases: [] };\n }\n\n const response = await fetchFn(RELEASES_URL);\n if (!response.ok) {\n throw new Error(`Failed to fetch release notes: GitHub API responded with ${response.status}`);\n }\n const allReleases = (await response.json()) as GitHubRelease[];\n\n const releases = allReleases\n .filter((r) => compareVersions(r.tag_name, fromVersion) > 0 && compareVersions(r.tag_name, toVersion) <= 0)\n .sort((a, b) => compareVersions(b.tag_name, a.tag_name));\n\n return { status: 'releases-found', fromVersion, toVersion, releases };\n}\n\nexport function formatReleaseNotes(result: ReleaseNotesResult): string {\n if (result.status === 'up-to-date') {\n return `You're on the latest clispark version (v${result.toVersion}), nothing to show.`;\n }\n if (result.releases.length === 0) {\n return `No published releases found between v${result.fromVersion} and v${result.toVersion}.`;\n }\n return result.releases.map((r) => `## ${r.tag_name}\\n\\n${r.body}`).join('\\n\\n');\n}\n","import { CORE_SCRIPT_NAMES, type Manifest } from './manifest';\nimport { deepEquals, reconcileEntry, stringEquals, type ReconcileOutcome } from './reconcile';\n\nexport interface PackageJsonShape {\n name: string;\n version: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n scripts?: Record<string, string>;\n engines?: Record<string, string>;\n oclif?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface FieldOutcome {\n key: string;\n outcome: ReconcileOutcome;\n}\n\nexport interface PackageJsonMergeResult {\n updatedPkg: PackageJsonShape;\n changed: boolean;\n dependencies: FieldOutcome[];\n scripts: FieldOutcome[];\n fields: FieldOutcome[];\n coreDependencies: Record<string, string>;\n coreScripts: Record<string, string>;\n coreFields: Manifest['coreFields'];\n}\n\nfunction currentDependencyValue(\n pkg: PackageJsonShape,\n name: string,\n): { section: 'dependencies' | 'devDependencies'; value: string } | undefined {\n if (pkg.dependencies?.[name] !== undefined) return { section: 'dependencies', value: pkg.dependencies[name] };\n if (pkg.devDependencies?.[name] !== undefined) {\n return { section: 'devDependencies', value: pkg.devDependencies[name] };\n }\n return undefined;\n}\n\nexport function mergePackageJson(\n currentPkg: PackageJsonShape,\n oldManifest: Manifest,\n newTemplatePkg: PackageJsonShape,\n): PackageJsonMergeResult {\n const updatedPkg: PackageJsonShape = { ...currentPkg };\n let changed = false;\n\n const dependencies: FieldOutcome[] = [];\n const coreDependencies: Record<string, string> = {};\n const dependencyNames = new Set([\n ...Object.keys(newTemplatePkg.dependencies ?? {}),\n ...Object.keys(newTemplatePkg.devDependencies ?? {}),\n ]);\n\n for (const name of dependencyNames) {\n const inNewDependencies = newTemplatePkg.dependencies?.[name] !== undefined;\n const newValue = (inNewDependencies ? newTemplatePkg.dependencies : newTemplatePkg.devDependencies)![name];\n const current = currentDependencyValue(currentPkg, name);\n const oldValue = oldManifest.coreDependencies[name];\n\n const result = reconcileEntry(current?.value, oldValue, newValue, stringEquals);\n dependencies.push({ key: name, outcome: result.outcome });\n coreDependencies[name] = result.value;\n\n if (result.outcome !== 'skipped' && result.value !== current?.value) {\n changed = true;\n const section = current?.section ?? (inNewDependencies ? 'dependencies' : 'devDependencies');\n updatedPkg[section] = { ...updatedPkg[section], [name]: result.value };\n }\n }\n\n const scripts: FieldOutcome[] = [];\n const coreScripts: Record<string, string> = {};\n\n for (const name of CORE_SCRIPT_NAMES) {\n const newValue = newTemplatePkg.scripts?.[name];\n if (newValue === undefined) continue;\n const currentValue = currentPkg.scripts?.[name];\n const oldValue = oldManifest.coreScripts[name];\n\n const result = reconcileEntry(currentValue, oldValue, newValue, stringEquals);\n scripts.push({ key: name, outcome: result.outcome });\n coreScripts[name] = result.value;\n\n if (result.outcome !== 'skipped' && result.value !== currentValue) {\n changed = true;\n updatedPkg.scripts = { ...updatedPkg.scripts, [name]: result.value };\n }\n }\n\n const fields: FieldOutcome[] = [];\n let enginesValue = oldManifest.coreFields.engines;\n let oclifValue = oldManifest.coreFields.oclif;\n\n if (newTemplatePkg.engines !== undefined) {\n const enginesResult = reconcileEntry(\n currentPkg.engines,\n oldManifest.coreFields.engines,\n newTemplatePkg.engines,\n deepEquals,\n );\n fields.push({ key: 'engines', outcome: enginesResult.outcome });\n enginesValue = enginesResult.value;\n if (enginesResult.outcome !== 'skipped' && !deepEquals(enginesResult.value, currentPkg.engines)) {\n changed = true;\n updatedPkg.engines = enginesResult.value;\n }\n }\n\n if (newTemplatePkg.oclif !== undefined) {\n const oclifResult = reconcileEntry(\n currentPkg.oclif,\n oldManifest.coreFields.oclif,\n newTemplatePkg.oclif,\n deepEquals,\n );\n fields.push({ key: 'oclif', outcome: oclifResult.outcome });\n oclifValue = oclifResult.value;\n if (oclifResult.outcome !== 'skipped' && !deepEquals(oclifResult.value, currentPkg.oclif)) {\n changed = true;\n updatedPkg.oclif = oclifResult.value;\n }\n }\n\n return {\n updatedPkg,\n changed,\n dependencies,\n scripts,\n fields,\n coreDependencies,\n coreScripts,\n coreFields: { engines: enginesValue, oclif: oclifValue },\n };\n}\n"],"mappings":"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","names":["path","fileURLToPath","path","readFile","writeFile","path","fileURLToPath","readFile","writeFile","path","mkdir","readFile","writeFile","path","spawn","defaultRunCommand","spawn","readFile","path","mkdir","writeFile","path"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/wizard.ts","../src/registry.ts","../src/scaffold.ts","../src/update/manifest.ts","../src/logger.ts","../src/update/update.ts","../src/update/reconcile.ts","../src/update/releasenotes.ts","../src/update/update-package-json.ts"],"sourcesContent":["// src/cli.ts\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { runWizard } from './wizard';\nimport { scaffoldProject } from './scaffold';\nimport { withLogging } from './logger';\nimport { formatUpdateSummary, updateProject } from './update/update';\nimport { fetchReleaseNotes, formatReleaseNotes } from './update/releasenotes';\nimport { getGeneratorVersion } from './update/manifest';\n\nconst program = new Command();\n\nprogram\n .name('clispark')\n .description('Interactive scaffolding tool for new CLI projects')\n .version(getGeneratorVersion());\n\nprogram.action(\n withLogging('scaffold', async (logger) => {\n const answers = await runWizard();\n const targetDir = path.join(process.cwd(), answers.projectName);\n\n logger.info({ projectName: answers.projectName, targetDir }, 'scaffold started');\n await scaffoldProject({\n projectName: answers.projectName,\n targetDir,\n registryUrl: answers.registryUrl,\n publishIntent: answers.publishIntent,\n });\n logger.info({ projectName: answers.projectName }, 'scaffold completed');\n\n console.log(`\\nDone! Your new CLI project is ready at ${targetDir}`);\n }),\n);\n\nprogram\n .command('update')\n .description('Update generator-managed core files and dependencies to the latest clispark version')\n .action(\n withLogging('update', async (logger) => {\n const targetDir = process.cwd();\n logger.info({ targetDir }, 'update started');\n const result = await updateProject(targetDir);\n logger.info({ status: result.status }, 'update completed');\n console.log(formatUpdateSummary(result));\n }),\n );\n\nprogram\n .command('releasenotes')\n .description(\"Show what changed between this project's generator version and the latest clispark version\")\n .action(\n withLogging('releasenotes', async (logger) => {\n const targetDir = process.cwd();\n logger.info({ targetDir }, 'releasenotes started');\n const result = await fetchReleaseNotes(targetDir);\n logger.info({ status: result.status }, 'releasenotes completed');\n console.log(formatReleaseNotes(result));\n }),\n );\n\nprogram.parseAsync(process.argv).catch((error: unknown) => {\n console.error(error);\n process.exit(1);\n});\n","// src/wizard.ts\nimport { intro, outro, text, select, log, isCancel, cancel } from '@clack/prompts';\nimport { checkPackageNameAvailability, DEFAULT_REGISTRY_URL, type NameCheckResult } from './registry';\nimport type { Profile, WizardAnswers } from './types';\n\nexport interface WizardDeps {\n checkAvailability: typeof checkPackageNameAvailability;\n}\n\nconst defaultDeps: WizardDeps = {\n checkAvailability: checkPackageNameAvailability,\n};\n\nfunction exitIfCancelled(value: unknown): void {\n if (isCancel(value)) {\n cancel('Operation cancelled.');\n process.exit(1);\n }\n}\n\nfunction validateProjectName(value: string): string | undefined {\n if (!value || value.trim().length === 0) return 'Project name is required.';\n if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {\n return 'Use lowercase letters and numbers, with single hyphens between words (no leading, trailing, or repeated hyphens).';\n }\n return undefined;\n}\n\nexport async function runWizard(deps: WizardDeps = defaultDeps): Promise<WizardAnswers> {\n intro('clispark — scaffold a new CLI project');\n\n const nameValue = await text({\n message: 'Project name',\n validate: validateProjectName,\n });\n exitIfCancelled(nameValue);\n let projectName = nameValue as string;\n\n const profileValue = await select({\n message: 'Is this a work or private project?',\n options: [\n { value: 'private', label: 'Private' },\n { value: 'work', label: 'Work' },\n ],\n });\n exitIfCancelled(profileValue);\n const profile = profileValue as Profile;\n\n let registryUrl = DEFAULT_REGISTRY_URL;\n if (profile === 'work') {\n const registryValue = await text({\n message: 'Custom npm registry URL (leave empty for npmjs.org)',\n placeholder: DEFAULT_REGISTRY_URL,\n defaultValue: DEFAULT_REGISTRY_URL,\n });\n exitIfCancelled(registryValue);\n registryUrl = (registryValue as string) || DEFAULT_REGISTRY_URL;\n }\n\n const publishIntentValue = await select({\n message: 'Do you plan to publish this to npm?',\n options: [\n { value: false, label: 'No' },\n { value: true, label: 'Yes' },\n ],\n initialValue: false,\n });\n exitIfCancelled(publishIntentValue);\n const publishIntent = publishIntentValue as boolean;\n\n let nameAvailability: NameCheckResult = 'skipped';\n\n if (publishIntent) {\n nameAvailability = await deps.checkAvailability(projectName, registryUrl);\n\n while (nameAvailability === 'taken') {\n log.warn(`\"${projectName}\" is already taken on ${registryUrl}. Please choose a different name.`);\n\n const retryValue = await text({\n message: 'Project name',\n validate: validateProjectName,\n });\n exitIfCancelled(retryValue);\n projectName = retryValue as string;\n\n nameAvailability = await deps.checkAvailability(projectName, registryUrl);\n }\n\n if (nameAvailability === 'unverified') {\n log.warn(`Could not verify availability of \"${projectName}\" on ${registryUrl}. Continuing anyway.`);\n }\n }\n\n outro(`Ready to scaffold \"${projectName}\".`);\n\n return { projectName, profile, registryUrl, publishIntent, nameAvailability };\n}\n","export const DEFAULT_REGISTRY_URL = 'https://registry.npmjs.org';\n\nexport type NameCheckResult = 'available' | 'taken' | 'unverified' | 'skipped';\n\nexport async function checkPackageNameAvailability(\n name: string,\n registryUrl: string = DEFAULT_REGISTRY_URL,\n): Promise<NameCheckResult> {\n const url = `${registryUrl.replace(/\\/$/, '')}/${encodeURIComponent(name)}`;\n\n try {\n const response = await fetch(url);\n if (response.status === 404) return 'available';\n if (response.status === 200) return 'taken';\n return 'unverified';\n } catch {\n return 'unverified';\n }\n}\n","import spawn from 'cross-spawn';\nimport { fileURLToPath } from 'node:url';\nimport path from 'node:path';\nimport { cp, readdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { DEFAULT_REGISTRY_URL } from './registry';\nimport { buildManifest, getGeneratorVersion, writeManifest } from './update/manifest';\n\nexport interface ScaffoldOptions {\n projectName: string;\n targetDir: string;\n registryUrl?: string;\n publishIntent?: boolean;\n}\n\nexport const TEMPLATE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'templates', 'base');\n\nasync function assertTargetDirIsUsable(targetDir: string): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(targetDir);\n } catch {\n return;\n }\n if (entries.length > 0) {\n throw new Error(`Directory \"${targetDir}\" already exists and is not empty.`);\n }\n}\n\nexport function applyPlaceholders(content: string, projectName: string): string {\n return content.replaceAll('{{projectName}}', projectName);\n}\n\nasync function replacePlaceholder(filePath: string, projectName: string): Promise<void> {\n const content = applyPlaceholders(await readFile(filePath, 'utf8'), projectName);\n await writeFile(filePath, content);\n}\n\nasync function markPackageJsonPrivate(targetDir: string): Promise<void> {\n const packageJsonPath = path.join(targetDir, 'package.json');\n const pkg = JSON.parse(await readFile(packageJsonPath, 'utf8')) as Record<string, unknown>;\n pkg.private = true;\n await writeFile(packageJsonPath, JSON.stringify(pkg, null, 2) + '\\n');\n}\n\nexport async function copyTemplate(options: ScaffoldOptions): Promise<void> {\n const { projectName, targetDir, registryUrl, publishIntent } = options;\n\n await assertTargetDirIsUsable(targetDir);\n await cp(TEMPLATE_DIR, targetDir, { recursive: true });\n\n await rename(path.join(targetDir, 'gitignore'), path.join(targetDir, '.gitignore'));\n\n await replacePlaceholder(path.join(targetDir, 'package.json'), projectName);\n await replacePlaceholder(path.join(targetDir, 'README.md'), projectName);\n await replacePlaceholder(path.join(targetDir, 'src', 'logger.ts'), projectName);\n await replacePlaceholder(path.join(targetDir, 'ARCHITECTURE.md'), projectName);\n\n if (publishIntent === false) {\n await markPackageJsonPrivate(targetDir);\n }\n\n if (registryUrl && registryUrl !== DEFAULT_REGISTRY_URL) {\n await writeFile(path.join(targetDir, '.npmrc'), `registry=${registryUrl}\\n`);\n }\n}\n\nexport interface ScaffoldDeps {\n runCommand: (command: string, args: string[], cwd: string) => Promise<void>;\n}\n\nasync function defaultRunCommand(command: string, args: string[], cwd: string): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const child = spawn(command, args, { cwd, stdio: 'inherit' });\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Command \"${command} ${args.join(' ')}\" exited with code ${code}`));\n }\n });\n });\n}\n\nconst defaultScaffoldDeps: ScaffoldDeps = { runCommand: defaultRunCommand };\n\nexport async function scaffoldProject(\n options: ScaffoldOptions,\n deps: ScaffoldDeps = defaultScaffoldDeps,\n): Promise<void> {\n await copyTemplate(options);\n\n const { targetDir } = options;\n const manifest = await buildManifest(targetDir, getGeneratorVersion());\n await writeManifest(targetDir, manifest);\n\n await deps.runCommand('git', ['init'], targetDir);\n await deps.runCommand('git', ['add', '-A'], targetDir);\n await deps.runCommand('git', ['commit', '-m', 'chore: initial scaffold from clispark'], targetDir);\n await deps.runCommand('npm', ['install'], targetDir);\n await deps.runCommand('npm', ['run', 'build'], targetDir);\n}\n","// src/update/manifest.ts\nimport { createHash } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport const CORE_FILE_PATHS = [\n 'bin/run.ts',\n 'src/index.ts',\n 'src/base-command.ts',\n 'src/logger.ts',\n 'tsup.config.ts',\n 'vitest.config.ts',\n 'tsconfig.json',\n 'ARCHITECTURE.md',\n '.gitignore',\n] as const;\n\nexport const CORE_SCRIPT_NAMES = ['build', 'postbuild', 'pretest', 'test', 'typecheck'] as const;\n\n/** The base template stores .gitignore as \"gitignore\" (renamed on copy); every other core path is identical in the template and in a generated project. */\nexport function templateSourcePath(relativePath: string): string {\n return relativePath === '.gitignore' ? 'gitignore' : relativePath;\n}\n\nexport interface Manifest {\n generatorVersion: string;\n coreFiles: Record<string, string>;\n coreDependencies: Record<string, string>;\n coreScripts: Record<string, string>;\n coreFields: {\n engines: Record<string, string>;\n oclif: Record<string, unknown>;\n };\n}\n\nexport interface PackageJsonCore {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n scripts?: Record<string, string>;\n engines?: Record<string, string>;\n oclif?: Record<string, unknown>;\n}\n\nexport function hashContent(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nexport async function hashCoreFiles(dir: string): Promise<Record<string, string>> {\n const hashes: Record<string, string> = {};\n for (const relativePath of CORE_FILE_PATHS) {\n const content = await readFile(path.join(dir, relativePath), 'utf8');\n hashes[relativePath] = hashContent(content);\n }\n return hashes;\n}\n\nexport function extractCoreFields(\n pkg: PackageJsonCore,\n): Pick<Manifest, 'coreDependencies' | 'coreScripts' | 'coreFields'> {\n const coreDependencies = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };\n\n const coreScripts: Record<string, string> = {};\n for (const name of CORE_SCRIPT_NAMES) {\n const value = pkg.scripts?.[name];\n if (value !== undefined) coreScripts[name] = value;\n }\n\n return {\n coreDependencies,\n coreScripts,\n coreFields: {\n engines: pkg.engines ?? {},\n oclif: pkg.oclif ?? {},\n },\n };\n}\n\nexport async function buildManifest(targetDir: string, generatorVersion: string): Promise<Manifest> {\n const coreFiles = await hashCoreFiles(targetDir);\n const pkg = JSON.parse(await readFile(path.join(targetDir, 'package.json'), 'utf8')) as PackageJsonCore;\n const { coreDependencies, coreScripts, coreFields } = extractCoreFields(pkg);\n return { generatorVersion, coreFiles, coreDependencies, coreScripts, coreFields };\n}\n\nexport const MANIFEST_RELATIVE_PATH = path.join('.clispark', 'manifest.json');\n\nexport async function writeManifest(targetDir: string, manifest: Manifest): Promise<void> {\n const manifestPath = path.join(targetDir, MANIFEST_RELATIVE_PATH);\n await mkdir(path.dirname(manifestPath), { recursive: true });\n await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\\n');\n}\n\nexport async function readManifest(targetDir: string): Promise<Manifest | undefined> {\n try {\n const content = await readFile(path.join(targetDir, MANIFEST_RELATIVE_PATH), 'utf8');\n return JSON.parse(content) as Manifest;\n } catch {\n return undefined;\n }\n}\n\nexport async function requireManifest(targetDir: string): Promise<Manifest> {\n const manifest = await readManifest(targetDir);\n if (!manifest) {\n throw new Error(\n 'No .clispark/manifest.json found — this project predates update support, or is not a clispark project.',\n );\n }\n return manifest;\n}\n\n/**\n * Finds clispark's own package.json by walking up from this file's location.\n * A fixed relative path (`../package.json`) can't work here: this module's\n * depth below the package root differs between running from source (tests)\n * and running as part of the bundled `dist/cli.js` (tsup flattens everything\n * into one file, so `import.meta.url` no longer reflects the original\n * per-module nesting).\n */\nexport function getGeneratorVersion(): string {\n let dir = path.dirname(fileURLToPath(import.meta.url));\n while (true) {\n const pkgPath = path.join(dir, 'package.json');\n if (existsSync(pkgPath)) {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string; version: string };\n if (pkg.name === 'clispark') return pkg.version;\n }\n const parentDir = path.dirname(dir);\n if (parentDir === dir) {\n throw new Error(\"Could not locate clispark's own package.json.\");\n }\n dir = parentDir;\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport { mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs';\nimport path from 'node:path';\nimport envPaths from 'env-paths';\nimport pino, { type Logger } from 'pino';\n\nconst paths = envPaths('clispark', { suffix: '' });\n\nexport interface LoggerHandle {\n logger: Logger;\n logFilePath: string;\n}\n\nfunction buildLogFileName(commandName: string): string {\n const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-');\n const suffix = randomBytes(3).toString('hex');\n return `${commandName}-${timestamp}-${suffix}.log`;\n}\n\nfunction getRetentionDays(): number {\n const parsed = Number(process.env.LOG_RETENTION_DAYS);\n return Number.isFinite(parsed) ? parsed : 14;\n}\n\n// Common secret-shaped field names, redacted at the top level and one level of\n// nesting (`*.<key>`). Edit this list directly if your own logging needs more.\nexport const SENSITIVE_LOG_KEYS = [\n 'password',\n 'secret',\n 'token',\n 'apiKey',\n 'accessToken',\n 'refreshToken',\n 'clientSecret',\n 'authorization',\n];\n\nfunction buildRedactPaths(keys: string[]): string[] {\n return keys.flatMap((key) => [key, `*.${key}`]);\n}\n\nfunction sweepOldLogs(logDir: string): void {\n try {\n const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1000;\n for (const file of readdirSync(logDir)) {\n const filePath = path.join(logDir, file);\n if (statSync(filePath).mtimeMs < cutoffMs) {\n unlinkSync(filePath);\n }\n }\n } catch {\n // best-effort cleanup; a broken sweep must never block the actual command\n }\n}\n\nexport function createLogger(commandName: string, logDir: string = paths.log): LoggerHandle {\n mkdirSync(logDir, { recursive: true });\n sweepOldLogs(logDir);\n\n const logFilePath = path.join(logDir, buildLogFileName(commandName));\n const fileDestination = pino.destination({ dest: logFilePath, sync: true, mode: 0o600 });\n const destination = process.env.DEBUG\n ? pino.multistream([{ stream: fileDestination }, { stream: process.stdout }])\n : fileDestination;\n const logger = pino({ redact: buildRedactPaths([...SENSITIVE_LOG_KEYS, 'registryUrl']) }, destination);\n\n return { logger, logFilePath };\n}\n\nexport function withLogging(\n commandName: string,\n action: (logger: Logger) => Promise<void>,\n logDir: string = paths.log,\n loggerFactory: typeof createLogger = createLogger,\n): () => Promise<void> {\n return async () => {\n let handle: LoggerHandle;\n try {\n handle = loggerFactory(commandName, logDir);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\\n✖ ${message}`);\n process.exit(1);\n return;\n }\n\n const { logger, logFilePath } = handle;\n try {\n logger.info({ command: commandName }, 'started');\n } catch {\n // best-effort logging; a write failure here must not abort a command that hasn't run yet\n }\n\n try {\n await action(logger);\n try {\n logger.info({ command: commandName }, 'completed');\n } catch {\n // best-effort logging; the command still succeeded\n }\n if (process.env.DEBUG) {\n console.log(`Details: ${logFilePath}`);\n }\n } catch (error) {\n try {\n logger.error({ command: commandName, err: error }, 'failed');\n } catch {\n // best-effort logging; never let a log-write failure mask the real error\n }\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\\n✖ ${message}`);\n console.error(`Details: ${logFilePath}`);\n process.exit(1);\n }\n };\n}\n","// src/update/update.ts\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport spawn from 'cross-spawn';\nimport { applyPlaceholders, TEMPLATE_DIR } from '../scaffold';\nimport {\n CORE_FILE_PATHS,\n getGeneratorVersion,\n hashContent,\n requireManifest,\n templateSourcePath,\n writeManifest,\n type Manifest,\n} from './manifest';\nimport { reconcileEntry, stringEquals } from './reconcile';\nimport { compareVersions } from './releasenotes';\nimport { mergePackageJson, type FieldOutcome, type PackageJsonShape } from './update-package-json';\n\nexport interface UpdateDeps {\n runCommand: (command: string, args: string[], cwd: string) => Promise<void>;\n captureCommand: (command: string, args: string[], cwd: string) => Promise<string>;\n}\n\nasync function defaultRunCommand(command: string, args: string[], cwd: string): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const child = spawn(command, args, { cwd, stdio: 'inherit' });\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) resolve();\n else reject(new Error(`Command \"${command} ${args.join(' ')}\" exited with code ${code}`));\n });\n });\n}\n\nasync function defaultCaptureCommand(command: string, args: string[], cwd: string): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const child = spawn(command, args, { cwd });\n let stdout = '';\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) resolve(stdout);\n else reject(new Error(`Command \"${command} ${args.join(' ')}\" exited with code ${code}`));\n });\n });\n}\n\nconst defaultUpdateDeps: UpdateDeps = { runCommand: defaultRunCommand, captureCommand: defaultCaptureCommand };\n\nexport interface FileOutcomeEntry {\n path: string;\n outcome: 'added' | 'replaced' | 'skipped' | 'no-longer-core';\n}\n\nexport interface UpdateResult {\n status: 'up-to-date' | 'no-changes' | 'updated';\n fromVersion: string;\n toVersion: string;\n files: FileOutcomeEntry[];\n dependencies: FieldOutcome[];\n scripts: FieldOutcome[];\n fields: FieldOutcome[];\n}\n\nexport async function updateProject(targetDir: string, deps: UpdateDeps = defaultUpdateDeps): Promise<UpdateResult> {\n const status = (await deps.captureCommand('git', ['status', '--porcelain'], targetDir)).trim();\n if (status.length > 0) {\n throw new Error('Working tree is not clean. Commit or stash your changes before running update.');\n }\n\n const oldManifest = await requireManifest(targetDir);\n const toVersion = getGeneratorVersion();\n const fromVersion = oldManifest.generatorVersion;\n\n if (compareVersions(fromVersion, toVersion) >= 0) {\n return { status: 'up-to-date', fromVersion, toVersion, files: [], dependencies: [], scripts: [], fields: [] };\n }\n\n const currentPkg = JSON.parse(await readFile(path.join(targetDir, 'package.json'), 'utf8')) as PackageJsonShape;\n const projectName = currentPkg.name;\n\n const newTemplatePkg = JSON.parse(\n applyPlaceholders(await readFile(path.join(TEMPLATE_DIR, 'package.json'), 'utf8'), projectName),\n ) as PackageJsonShape;\n\n const files: FileOutcomeEntry[] = [];\n const newCoreFiles: Record<string, string> = {};\n const fileWrites: { targetPath: string; content: string }[] = [];\n\n for (const relativePath of CORE_FILE_PATHS) {\n const newContent = applyPlaceholders(\n await readFile(path.join(TEMPLATE_DIR, templateSourcePath(relativePath)), 'utf8'),\n projectName,\n );\n const newHash = hashContent(newContent);\n\n let currentHash: string | undefined;\n try {\n currentHash = hashContent(await readFile(path.join(targetDir, relativePath), 'utf8'));\n } catch {\n currentHash = undefined;\n }\n\n const result = reconcileEntry(currentHash, oldManifest.coreFiles[relativePath], newHash, stringEquals);\n files.push({ path: relativePath, outcome: result.outcome });\n newCoreFiles[relativePath] = result.value;\n\n if (result.outcome === 'added' || result.outcome === 'replaced') {\n fileWrites.push({ targetPath: path.join(targetDir, relativePath), content: newContent });\n }\n }\n\n for (const relativePath of Object.keys(oldManifest.coreFiles)) {\n if ((CORE_FILE_PATHS as readonly string[]).includes(relativePath)) continue;\n try {\n await readFile(path.join(targetDir, relativePath), 'utf8');\n files.push({ path: relativePath, outcome: 'no-longer-core' });\n } catch {\n // already gone locally, nothing to report\n }\n }\n\n const pkgMerge = mergePackageJson(currentPkg, oldManifest, newTemplatePkg);\n\n const hasFileChanges = files.some(\n (f) => f.outcome === 'added' || f.outcome === 'replaced' || f.outcome === 'no-longer-core',\n );\n const hasChanges = hasFileChanges || pkgMerge.changed;\n\n if (!hasChanges) {\n return {\n status: 'no-changes',\n fromVersion,\n toVersion,\n files,\n dependencies: pkgMerge.dependencies,\n scripts: pkgMerge.scripts,\n fields: pkgMerge.fields,\n };\n }\n\n for (const write of fileWrites) {\n await mkdir(path.dirname(write.targetPath), { recursive: true });\n await writeFile(write.targetPath, write.content);\n }\n\n if (pkgMerge.changed) {\n await writeFile(path.join(targetDir, 'package.json'), JSON.stringify(pkgMerge.updatedPkg, null, 2) + '\\n');\n }\n\n const newManifest: Manifest = {\n generatorVersion: toVersion,\n coreFiles: newCoreFiles,\n coreDependencies: pkgMerge.coreDependencies,\n coreScripts: pkgMerge.coreScripts,\n coreFields: pkgMerge.coreFields,\n };\n await writeManifest(targetDir, newManifest);\n\n await deps.runCommand('git', ['add', '-A'], targetDir);\n await deps.runCommand('git', ['commit', '-m', `chore: update clispark core to v${toVersion}`], targetDir);\n\n return {\n status: 'updated',\n fromVersion,\n toVersion,\n files,\n dependencies: pkgMerge.dependencies,\n scripts: pkgMerge.scripts,\n fields: pkgMerge.fields,\n };\n}\n\nexport function formatUpdateSummary(result: UpdateResult): string {\n if (result.status === 'up-to-date') {\n return `Already up to date (v${result.toVersion}).`;\n }\n\n const added = result.files.filter((f) => f.outcome === 'added');\n const replaced = result.files.filter((f) => f.outcome === 'replaced');\n const skipped = result.files.filter((f) => f.outcome === 'skipped');\n const noLongerCore = result.files.filter((f) => f.outcome === 'no-longer-core');\n\n const lines: string[] = [];\n if (result.status === 'no-changes') {\n lines.push(\n noLongerCore.length\n ? `No changes applied: nothing to update since v${result.fromVersion}.`\n : `No changes applied: every core file/field has been modified locally since v${result.fromVersion}.`,\n );\n } else {\n lines.push(`Updated core from v${result.fromVersion} to v${result.toVersion}.`);\n }\n\n if (added.length) lines.push(` New: ${added.map((f) => f.path).join(', ')}`);\n if (replaced.length) lines.push(` Updated: ${replaced.map((f) => f.path).join(', ')}`);\n if (skipped.length) lines.push(` Skipped (locally modified): ${skipped.map((f) => f.path).join(', ')}`);\n if (noLongerCore.length) {\n lines.push(\n ` No longer part of the core, safe to remove manually: ${noLongerCore.map((f) => f.path).join(', ')}`,\n );\n }\n\n const fieldOutcomes = [...result.dependencies, ...result.scripts, ...result.fields].filter(\n (o) => o.outcome !== 'skipped',\n );\n if (fieldOutcomes.length) {\n lines.push(` package.json: ${fieldOutcomes.map((o) => `${o.key} (${o.outcome})`).join(', ')}`);\n }\n\n if (result.status === 'updated') {\n lines.push('Run \"clispark releasenotes\" to see what changed.');\n }\n\n return lines.join('\\n');\n}\n","export type ReconcileOutcome = 'added' | 'replaced' | 'skipped';\n\nexport interface ReconcileResult<T> {\n outcome: ReconcileOutcome;\n value: T;\n}\n\nexport function reconcileEntry<T>(\n currentLiveValue: T | undefined,\n oldManifestValue: T | undefined,\n newTemplateValue: T,\n isEqual: (a: T, b: T) => boolean,\n): ReconcileResult<T> {\n if (currentLiveValue === undefined) {\n return { outcome: 'added', value: newTemplateValue };\n }\n if (oldManifestValue !== undefined && isEqual(currentLiveValue, oldManifestValue)) {\n return { outcome: 'replaced', value: newTemplateValue };\n }\n return { outcome: 'skipped', value: oldManifestValue ?? currentLiveValue };\n}\n\nexport function stringEquals(a: string, b: string): boolean {\n return a === b;\n}\n\nexport function deepEquals(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n","// src/update/releasenotes.ts\nimport { getGeneratorVersion, requireManifest } from './manifest';\n\nexport interface GitHubRelease {\n tag_name: string;\n name: string;\n body: string;\n}\n\nfunction stripV(version: string): string {\n return version.startsWith('v') ? version.slice(1) : version;\n}\n\nexport function compareVersions(a: string, b: string): number {\n const partsA = stripV(a).split('.').map(Number);\n const partsB = stripV(b).split('.').map(Number);\n const length = Math.max(partsA.length, partsB.length);\n for (let i = 0; i < length; i++) {\n const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);\n if (diff !== 0) return diff;\n }\n return 0;\n}\n\nexport interface ReleaseNotesResult {\n status: 'up-to-date' | 'releases-found';\n fromVersion: string;\n toVersion: string;\n releases: GitHubRelease[];\n}\n\nconst RELEASES_URL = 'https://api.github.com/repos/martinwichner/clispark/releases';\n\nexport async function fetchReleaseNotes(\n targetDir: string,\n fetchFn: typeof fetch = fetch,\n): Promise<ReleaseNotesResult> {\n const manifest = await requireManifest(targetDir);\n const fromVersion = manifest.generatorVersion;\n const toVersion = getGeneratorVersion();\n\n if (compareVersions(fromVersion, toVersion) >= 0) {\n return { status: 'up-to-date', fromVersion, toVersion, releases: [] };\n }\n\n const response = await fetchFn(RELEASES_URL);\n if (!response.ok) {\n throw new Error(`Failed to fetch release notes: GitHub API responded with ${response.status}`);\n }\n const allReleases = (await response.json()) as GitHubRelease[];\n\n const releases = allReleases\n .filter((r) => compareVersions(r.tag_name, fromVersion) > 0 && compareVersions(r.tag_name, toVersion) <= 0)\n .sort((a, b) => compareVersions(b.tag_name, a.tag_name));\n\n return { status: 'releases-found', fromVersion, toVersion, releases };\n}\n\nexport function formatReleaseNotes(result: ReleaseNotesResult): string {\n if (result.status === 'up-to-date') {\n return `You're on the latest clispark version (v${result.toVersion}), nothing to show.`;\n }\n if (result.releases.length === 0) {\n return `No published releases found between v${result.fromVersion} and v${result.toVersion}.`;\n }\n return result.releases.map((r) => `## ${r.tag_name}\\n\\n${r.body}`).join('\\n\\n');\n}\n","import { CORE_SCRIPT_NAMES, type Manifest } from './manifest';\nimport { deepEquals, reconcileEntry, stringEquals, type ReconcileOutcome } from './reconcile';\n\nexport interface PackageJsonShape {\n name: string;\n version: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n scripts?: Record<string, string>;\n engines?: Record<string, string>;\n oclif?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface FieldOutcome {\n key: string;\n outcome: ReconcileOutcome;\n}\n\nexport interface PackageJsonMergeResult {\n updatedPkg: PackageJsonShape;\n changed: boolean;\n dependencies: FieldOutcome[];\n scripts: FieldOutcome[];\n fields: FieldOutcome[];\n coreDependencies: Record<string, string>;\n coreScripts: Record<string, string>;\n coreFields: Manifest['coreFields'];\n}\n\nfunction currentDependencyValue(\n pkg: PackageJsonShape,\n name: string,\n): { section: 'dependencies' | 'devDependencies'; value: string } | undefined {\n if (pkg.dependencies?.[name] !== undefined) return { section: 'dependencies', value: pkg.dependencies[name] };\n if (pkg.devDependencies?.[name] !== undefined) {\n return { section: 'devDependencies', value: pkg.devDependencies[name] };\n }\n return undefined;\n}\n\nexport function mergePackageJson(\n currentPkg: PackageJsonShape,\n oldManifest: Manifest,\n newTemplatePkg: PackageJsonShape,\n): PackageJsonMergeResult {\n const updatedPkg: PackageJsonShape = { ...currentPkg };\n let changed = false;\n\n const dependencies: FieldOutcome[] = [];\n const coreDependencies: Record<string, string> = {};\n const dependencyNames = new Set([\n ...Object.keys(newTemplatePkg.dependencies ?? {}),\n ...Object.keys(newTemplatePkg.devDependencies ?? {}),\n ]);\n\n for (const name of dependencyNames) {\n const inNewDependencies = newTemplatePkg.dependencies?.[name] !== undefined;\n const newValue = (inNewDependencies ? newTemplatePkg.dependencies : newTemplatePkg.devDependencies)![name];\n const current = currentDependencyValue(currentPkg, name);\n const oldValue = oldManifest.coreDependencies[name];\n\n const result = reconcileEntry(current?.value, oldValue, newValue, stringEquals);\n dependencies.push({ key: name, outcome: result.outcome });\n coreDependencies[name] = result.value;\n\n if (result.outcome !== 'skipped' && result.value !== current?.value) {\n changed = true;\n const section = current?.section ?? (inNewDependencies ? 'dependencies' : 'devDependencies');\n updatedPkg[section] = { ...updatedPkg[section], [name]: result.value };\n }\n }\n\n const scripts: FieldOutcome[] = [];\n const coreScripts: Record<string, string> = {};\n\n for (const name of CORE_SCRIPT_NAMES) {\n const newValue = newTemplatePkg.scripts?.[name];\n if (newValue === undefined) continue;\n const currentValue = currentPkg.scripts?.[name];\n const oldValue = oldManifest.coreScripts[name];\n\n const result = reconcileEntry(currentValue, oldValue, newValue, stringEquals);\n scripts.push({ key: name, outcome: result.outcome });\n coreScripts[name] = result.value;\n\n if (result.outcome !== 'skipped' && result.value !== currentValue) {\n changed = true;\n updatedPkg.scripts = { ...updatedPkg.scripts, [name]: result.value };\n }\n }\n\n const fields: FieldOutcome[] = [];\n let enginesValue = oldManifest.coreFields.engines;\n let oclifValue = oldManifest.coreFields.oclif;\n\n if (newTemplatePkg.engines !== undefined) {\n const enginesResult = reconcileEntry(\n currentPkg.engines,\n oldManifest.coreFields.engines,\n newTemplatePkg.engines,\n deepEquals,\n );\n fields.push({ key: 'engines', outcome: enginesResult.outcome });\n enginesValue = enginesResult.value;\n if (enginesResult.outcome !== 'skipped' && !deepEquals(enginesResult.value, currentPkg.engines)) {\n changed = true;\n updatedPkg.engines = enginesResult.value;\n }\n }\n\n if (newTemplatePkg.oclif !== undefined) {\n const oclifResult = reconcileEntry(\n currentPkg.oclif,\n oldManifest.coreFields.oclif,\n newTemplatePkg.oclif,\n deepEquals,\n );\n fields.push({ key: 'oclif', outcome: oclifResult.outcome });\n oclifValue = oclifResult.value;\n if (oclifResult.outcome !== 'skipped' && !deepEquals(oclifResult.value, currentPkg.oclif)) {\n changed = true;\n updatedPkg.oclif = oclifResult.value;\n }\n }\n\n return {\n updatedPkg,\n changed,\n dependencies,\n scripts,\n fields,\n coreDependencies,\n coreScripts,\n coreFields: { engines: enginesValue, oclif: oclifValue },\n };\n}\n"],"mappings":"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","names":["path","fileURLToPath","path","readFile","writeFile","path","fileURLToPath","readFile","writeFile","path","mkdir","readFile","writeFile","path","spawn","defaultRunCommand","spawn","readFile","path","mkdir","writeFile","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clispark",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "description": "Interactive scaffolding tool for new CLI projects",
5
5
  "keywords": [
6
6
  "cli",
@@ -73,7 +73,14 @@ oclif discovers commands at runtime from the `oclif.commands` path in `package.j
73
73
 
74
74
  ## Logging
75
75
 
76
- `src/logger.ts` writes structured JSON logs via `pino`, one file per command invocation, to an OS-appropriate log directory (via `env-paths`) — never to the project's own working directory. Every log line includes the command name. On failure, the full error (including stack trace) is logged, while the terminal only ever shows a clean `Error: <message>` — never a raw stack trace.
76
+ `src/logger.ts` writes structured JSON logs via `pino`, one file per command invocation, to an OS-appropriate log directory (via `env-paths`) — never to the project's own working directory. Every log line includes the command name. On failure, the full error (including stack trace) is logged, while the terminal only ever shows a clean `Error: <message>` — never a raw stack trace; the terminal also prints `Details: <path to the log file>` so the full error is one file away.
77
+
78
+ A few things run automatically around every log call:
79
+
80
+ - **Retention:** before opening a new log file, `sweepOldLogs()` deletes files older than `LOG_RETENTION_DAYS` (default `14`).
81
+ - **Redaction:** `pino`'s `redact` option is configured with `SENSITIVE_LOG_KEYS` — a plain, exported array of secret-shaped field names (`password`, `token`, `apiKey`, etc., matched at the top level and one level of nesting). It's just a `const` in `src/logger.ts`; add to it directly if your commands log other sensitive fields.
82
+ - **`DEBUG=1`:** streams every log line to stdout live (via `pino.multistream()`) in addition to the file, and prints `Details: <path>` on success too, not just on failure — useful while developing a new command.
83
+ - **File permissions:** the log file itself is created with mode `0o600` (owner read/write only); this is a no-op on Windows, which has no POSIX permission bits.
77
84
 
78
85
  ## Testing
79
86
 
@@ -4,7 +4,9 @@ Generated with [clispark](https://github.com/martinwichner/clispark).
4
4
 
5
5
  ## Requirements
6
6
 
7
- Node.js **>=24** — this project's entry point (`bin/run.ts`) runs directly via Node's native TypeScript execution, with no build step. On an older Node version it fails with an `ERR_UNKNOWN_FILE_EXTENSION` error rather than a clear version message, so if you hit that, check `node --version` first.
7
+ Node.js **>=24** — the CLI entry point (`bin/run.ts`) runs directly via Node's native TypeScript execution, with no separate build step for that file. On an older Node version it fails with an `ERR_UNKNOWN_FILE_EXTENSION` error rather than a clear version message, so if you hit that, check `node --version` first.
8
+
9
+ Commands themselves are still discovered from the compiled `dist/commands` output (`oclif.commands` in `package.json`), so a build is still required at least once before any command runs — clispark already does this for you during scaffolding (`npm install && npm run build`). You'll only need to run `npm run build` again yourself after a fresh clone, or if `dist/` gets deleted.
8
10
 
9
11
  ## Example commands
10
12
 
@@ -21,3 +23,11 @@ Two example commands ship in `src/commands/` as copy-paste starting points:
21
23
  node bin/run.ts task list groceries
22
24
  ```
23
25
  See `ARCHITECTURE.md`'s "Argument Types" section for the full set of argument types oclif supports, including a couple this example doesn't use.
26
+
27
+ ## Logging & debugging
28
+
29
+ Every command run writes a structured JSON log file (one per invocation, in an OS-appropriate log directory — see `ARCHITECTURE.md`'s "Logging" section). By default the terminal only shows a clean `Error: <message>` on failure, or nothing on success.
30
+
31
+ - **`DEBUG=1`** — streams the raw JSON log lines to stdout live as the command runs, and prints `Details: <path>` to the log file on both success and failure (normally that line only appears on failure).
32
+ - **`LOG_RETENTION_DAYS`** — log files older than this many days are swept before each new run (default: `14`).
33
+ - Fields that look like secrets (`password`, `token`, `apiKey`, etc. — see `SENSITIVE_LOG_KEYS` in `src/logger.ts`) are redacted from log output automatically; edit that list directly in your own copy if you log other sensitive fields.
@@ -53,21 +53,21 @@ describe('createLogger', () => {
53
53
  expect(entry.command).toBe('hello');
54
54
  });
55
55
 
56
- it('redacts registryUrl values, including one level of nesting', async () => {
56
+ it('redacts generic secret-shaped fields, including one level of nesting', async () => {
57
57
  const { logger, logFilePath } = createLogger('hello', tmpRoot);
58
58
 
59
59
  logger.info(
60
60
  {
61
- registryUrl: 'https://registry.example.com/secret-token',
62
- nested: { registryUrl: 'https://nested.example.com/other-secret' },
61
+ token: 'ghp_super-secret-value',
62
+ nested: { apiKey: 'sk-another-secret-value' },
63
63
  },
64
64
  'started',
65
65
  );
66
66
  await logger.flush();
67
67
 
68
68
  const content = await readFile(logFilePath, 'utf8');
69
- expect(content).not.toContain('secret-token');
70
- expect(content).not.toContain('nested.example.com');
69
+ expect(content).not.toContain('ghp_super-secret-value');
70
+ expect(content).not.toContain('sk-another-secret-value');
71
71
  expect(content).toContain('[Redacted]');
72
72
  });
73
73
 
@@ -23,6 +23,23 @@ function getRetentionDays(): number {
23
23
  return Number.isFinite(parsed) ? parsed : 14;
24
24
  }
25
25
 
26
+ // Common secret-shaped field names, redacted at the top level and one level of
27
+ // nesting (`*.<key>`). Edit this list directly if your own logging needs more.
28
+ export const SENSITIVE_LOG_KEYS = [
29
+ 'password',
30
+ 'secret',
31
+ 'token',
32
+ 'apiKey',
33
+ 'accessToken',
34
+ 'refreshToken',
35
+ 'clientSecret',
36
+ 'authorization',
37
+ ];
38
+
39
+ function buildRedactPaths(keys: string[]): string[] {
40
+ return keys.flatMap((key) => [key, `*.${key}`]);
41
+ }
42
+
26
43
  function sweepOldLogs(logDir: string): void {
27
44
  try {
28
45
  const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1000;
@@ -46,7 +63,7 @@ export function createLogger(commandName: string, logDir: string = paths.log): L
46
63
  const destination = process.env.DEBUG
47
64
  ? pino.multistream([{ stream: fileDestination }, { stream: process.stdout }])
48
65
  : fileDestination;
49
- const logger = pino({ redact: ['registryUrl', '*.registryUrl'] }, destination);
66
+ const logger = pino({ redact: buildRedactPaths(SENSITIVE_LOG_KEYS) }, destination);
50
67
 
51
68
  return { logger, logFilePath };
52
69
  }