clispark 1.7.1 → 1.8.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
@@ -29,7 +29,7 @@ Every generated project includes:
29
29
  - **Structured logging** (`pino`, one log file per invocation in an OS-appropriate log directory) that automatically covers every command
30
30
  - **Consistent error handling** with no opt-out — clean `Error: <message>` output on failure, full stack trace captured in the log file
31
31
  - **A working test setup** (`vitest` + `@oclif/test`) with an example test to copy from
32
- - **Example commands** — a minimal `hello` starting point plus a `task`/`task complete`/`task list` reference covering required args, optional args, enum-constrained args, integer and boolean args, and subcommands
32
+ - **Example commands** — a minimal `hello` starting point plus a `task`/`task complete`/`task list` reference covering required args, optional args, enum-constrained args, an integer arg, a boolean flag, and subcommands
33
33
  - **A clean build pipeline** (`tsup`) producing a directly runnable binary
34
34
 
35
35
  ## Usage
package/dist/cli.js CHANGED
@@ -109,6 +109,16 @@ import { existsSync, readFileSync } from "fs";
109
109
  import { mkdir, readFile, writeFile } from "fs/promises";
110
110
  import path from "path";
111
111
  import { fileURLToPath } from "url";
112
+
113
+ // src/errors.ts
114
+ var UserError = class extends Error {
115
+ constructor(message) {
116
+ super(message);
117
+ this.name = "UserError";
118
+ }
119
+ };
120
+
121
+ // src/update/manifest.ts
112
122
  var CORE_FILE_PATHS = [
113
123
  "bin/run.ts",
114
124
  "src/index.ts",
@@ -128,12 +138,13 @@ function hashContent(content) {
128
138
  return createHash("sha256").update(content).digest("hex");
129
139
  }
130
140
  async function hashCoreFiles(dir) {
131
- const hashes = {};
132
- for (const relativePath of CORE_FILE_PATHS) {
133
- const content = await readFile(path.join(dir, relativePath), "utf8");
134
- hashes[relativePath] = hashContent(content);
135
- }
136
- return hashes;
141
+ const entries = await Promise.all(
142
+ CORE_FILE_PATHS.map(async (relativePath) => {
143
+ const content = await readFile(path.join(dir, relativePath), "utf8");
144
+ return [relativePath, hashContent(content)];
145
+ })
146
+ );
147
+ return Object.fromEntries(entries);
137
148
  }
138
149
  function extractCoreFields(pkg) {
139
150
  const coreDependencies = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
@@ -174,7 +185,7 @@ async function readManifest(targetDir) {
174
185
  async function requireManifest(targetDir) {
175
186
  const manifest = await readManifest(targetDir);
176
187
  if (!manifest) {
177
- throw new Error(
188
+ throw new UserError(
178
189
  "No .clispark/manifest.json found \u2014 this project predates update support, or is not a clispark project."
179
190
  );
180
191
  }
@@ -206,15 +217,32 @@ async function assertTargetDirIsUsable(targetDir) {
206
217
  return;
207
218
  }
208
219
  if (entries.length > 0) {
209
- throw new Error(`Directory "${targetDir}" already exists and is not empty.`);
220
+ throw new UserError(`Directory "${targetDir}" already exists and is not empty.`);
210
221
  }
211
222
  }
212
223
  function applyPlaceholders(content, projectName) {
213
224
  return content.replaceAll("{{projectName}}", projectName);
214
225
  }
215
- async function replacePlaceholder(filePath, projectName) {
216
- const content = applyPlaceholders(await readFile2(filePath, "utf8"), projectName);
217
- await writeFile2(filePath, content);
226
+ async function collectFiles(dir) {
227
+ const entries = await readdir(dir, { withFileTypes: true });
228
+ const files = await Promise.all(
229
+ entries.map((entry) => {
230
+ const fullPath = path2.join(dir, entry.name);
231
+ return entry.isDirectory() ? collectFiles(fullPath) : Promise.resolve([fullPath]);
232
+ })
233
+ );
234
+ return files.flat();
235
+ }
236
+ async function replacePlaceholdersInTree(targetDir, projectName) {
237
+ const files = await collectFiles(targetDir);
238
+ await Promise.all(
239
+ files.map(async (filePath) => {
240
+ const content = await readFile2(filePath, "utf8");
241
+ if (content.includes("{{projectName}}")) {
242
+ await writeFile2(filePath, applyPlaceholders(content, projectName));
243
+ }
244
+ })
245
+ );
218
246
  }
219
247
  async function markPackageJsonPrivate(targetDir) {
220
248
  const packageJsonPath = path2.join(targetDir, "package.json");
@@ -227,10 +255,7 @@ async function copyTemplate(options) {
227
255
  await assertTargetDirIsUsable(targetDir);
228
256
  await cp(TEMPLATE_DIR, targetDir, { recursive: true });
229
257
  await rename(path2.join(targetDir, "gitignore"), path2.join(targetDir, ".gitignore"));
230
- await replacePlaceholder(path2.join(targetDir, "package.json"), projectName);
231
- await replacePlaceholder(path2.join(targetDir, "README.md"), projectName);
232
- await replacePlaceholder(path2.join(targetDir, "src", "logger.ts"), projectName);
233
- await replacePlaceholder(path2.join(targetDir, "ARCHITECTURE.md"), projectName);
258
+ await replacePlaceholdersInTree(targetDir, projectName);
234
259
  if (publishIntent === false) {
235
260
  await markPackageJsonPrivate(targetDir);
236
261
  }
@@ -272,6 +297,12 @@ import path3 from "path";
272
297
  import envPaths from "env-paths";
273
298
  import pino from "pino";
274
299
  var paths = envPaths("clispark", { suffix: "" });
300
+ function safely(fn) {
301
+ try {
302
+ fn();
303
+ } catch {
304
+ }
305
+ }
275
306
  function buildLogFileName(commandName) {
276
307
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-");
277
308
  const suffix = randomBytes(3).toString("hex");
@@ -297,12 +328,14 @@ function buildRedactPaths(keys) {
297
328
  var SWEEP_MARKER_FILE = ".last-sweep";
298
329
  var SWEEP_THROTTLE_MS = 24 * 60 * 60 * 1e3;
299
330
  function sweepOldLogs(logDir) {
300
- try {
331
+ safely(() => {
301
332
  const markerPath = path3.join(logDir, SWEEP_MARKER_FILE);
333
+ let shouldSweep = true;
302
334
  try {
303
- if (Date.now() - statSync(markerPath).mtimeMs < SWEEP_THROTTLE_MS) return;
335
+ shouldSweep = Date.now() - statSync(markerPath).mtimeMs >= SWEEP_THROTTLE_MS;
304
336
  } catch {
305
337
  }
338
+ if (!shouldSweep) return;
306
339
  const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1e3;
307
340
  for (const file of readdirSync(logDir)) {
308
341
  if (file === SWEEP_MARKER_FILE) continue;
@@ -312,8 +345,7 @@ function sweepOldLogs(logDir) {
312
345
  }
313
346
  }
314
347
  writeFileSync(markerPath, "");
315
- } catch {
316
- }
348
+ });
317
349
  }
318
350
  function createLogger(commandName, logDir = paths.log) {
319
351
  mkdirSync(logDir, { recursive: true });
@@ -337,24 +369,15 @@ function withLogging(commandName, action, logDir = paths.log, loggerFactory = cr
337
369
  return;
338
370
  }
339
371
  const { logger, logFilePath } = handle;
340
- try {
341
- logger.info({ command: commandName }, "started");
342
- } catch {
343
- }
372
+ safely(() => logger.info({ command: commandName }, "started"));
344
373
  try {
345
374
  await action(logger);
346
- try {
347
- logger.info({ command: commandName }, "completed");
348
- } catch {
349
- }
375
+ safely(() => logger.info({ command: commandName }, "completed"));
350
376
  if (process.env.DEBUG) {
351
377
  console.log(`Details: ${logFilePath}`);
352
378
  }
353
379
  } catch (error) {
354
- try {
355
- logger.error({ command: commandName, err: error }, "failed");
356
- } catch {
357
- }
380
+ safely(() => logger.error({ command: commandName, err: error }, "failed"));
358
381
  const message = error instanceof Error ? error.message : String(error);
359
382
  console.error(`
360
383
  \u2716 ${message}`);
@@ -547,7 +570,7 @@ var defaultUpdateDeps = { runCommand: defaultRunCommand2, captureCommand: defaul
547
570
  async function updateProject(targetDir, deps = defaultUpdateDeps) {
548
571
  const status = (await deps.captureCommand("git", ["status", "--porcelain"], targetDir)).trim();
549
572
  if (status.length > 0) {
550
- throw new Error("Working tree is not clean. Commit or stash your changes before running update.");
573
+ throw new UserError("Working tree is not clean. Commit or stash your changes before running update.");
551
574
  }
552
575
  const oldManifest = await requireManifest(targetDir);
553
576
  const toVersion = getGeneratorVersion();
@@ -563,19 +586,24 @@ async function updateProject(targetDir, deps = defaultUpdateDeps) {
563
586
  const files = [];
564
587
  const newCoreFiles = {};
565
588
  const fileWrites = [];
566
- for (const relativePath of CORE_FILE_PATHS) {
567
- const newContent = applyPlaceholders(
568
- await readFile3(path4.join(TEMPLATE_DIR, templateSourcePath(relativePath)), "utf8"),
569
- projectName
570
- );
571
- const newHash = hashContent(newContent);
572
- let currentHash;
573
- try {
574
- currentHash = hashContent(await readFile3(path4.join(targetDir, relativePath), "utf8"));
575
- } catch {
576
- currentHash = void 0;
577
- }
578
- const result = reconcileEntry(currentHash, oldManifest.coreFiles[relativePath], newHash, stringEquals);
589
+ const perFileResults = await Promise.all(
590
+ CORE_FILE_PATHS.map(async (relativePath) => {
591
+ const newContent = applyPlaceholders(
592
+ await readFile3(path4.join(TEMPLATE_DIR, templateSourcePath(relativePath)), "utf8"),
593
+ projectName
594
+ );
595
+ const newHash = hashContent(newContent);
596
+ let currentHash;
597
+ try {
598
+ currentHash = hashContent(await readFile3(path4.join(targetDir, relativePath), "utf8"));
599
+ } catch {
600
+ currentHash = void 0;
601
+ }
602
+ const result = reconcileEntry(currentHash, oldManifest.coreFiles[relativePath], newHash, stringEquals);
603
+ return { relativePath, newContent, result };
604
+ })
605
+ );
606
+ for (const { relativePath, newContent, result } of perFileResults) {
579
607
  files.push({ path: relativePath, outcome: result.outcome });
580
608
  newCoreFiles[relativePath] = result.value;
581
609
  if (result.outcome === "added" || result.outcome === "replaced") {
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({\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\nconst FETCH_TIMEOUT_MS = 5000;\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, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\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, writeFileSync } 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\nconst SWEEP_MARKER_FILE = '.last-sweep';\nconst SWEEP_THROTTLE_MS = 24 * 60 * 60 * 1000; // once a day is enough given day-granularity retention\n\nfunction sweepOldLogs(logDir: string): void {\n try {\n const markerPath = path.join(logDir, SWEEP_MARKER_FILE);\n try {\n if (Date.now() - statSync(markerPath).mtimeMs < SWEEP_THROTTLE_MS) return;\n } catch {\n // no marker yet (first run in this directory) - sweep now\n }\n\n const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1000;\n for (const file of readdirSync(logDir)) {\n if (file === SWEEP_MARKER_FILE) continue;\n const filePath = path.join(logDir, file);\n if (statSync(filePath).mtimeMs < cutoffMs) {\n unlinkSync(filePath);\n }\n }\n writeFileSync(markerPath, '');\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';\nconst FETCH_TIMEOUT_MS = 5000;\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, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\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","FETCH_TIMEOUT_MS","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/errors.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\nconst FETCH_TIMEOUT_MS = 5000;\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, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\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';\nimport { UserError } from './errors';\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 UserError(`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 collectFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files = await Promise.all(\n entries.map((entry) => {\n const fullPath = path.join(dir, entry.name);\n return entry.isDirectory() ? collectFiles(fullPath) : Promise.resolve([fullPath]);\n }),\n );\n return files.flat();\n}\n\n// Scans every copied file rather than a hardcoded list, so a new template file\n// that needs {{projectName}} substituted can't silently be forgotten here.\nasync function replacePlaceholdersInTree(targetDir: string, projectName: string): Promise<void> {\n const files = await collectFiles(targetDir);\n await Promise.all(\n files.map(async (filePath) => {\n const content = await readFile(filePath, 'utf8');\n if (content.includes('{{projectName}}')) {\n await writeFile(filePath, applyPlaceholders(content, projectName));\n }\n }),\n );\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 replacePlaceholdersInTree(targetDir, 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';\nimport { UserError } from '../errors';\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 entries = await Promise.all(\n CORE_FILE_PATHS.map(async (relativePath) => {\n const content = await readFile(path.join(dir, relativePath), 'utf8');\n return [relativePath, hashContent(content)] as const;\n }),\n );\n return Object.fromEntries(entries);\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 UserError(\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","// src/errors.ts\n\n/** An expected, user-fixable failure (bad input, unmet precondition) — distinct from an unexpected crash. */\nexport class UserError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UserError';\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport { mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } 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\n/** Runs fn, silently swallowing any error — for best-effort work that must never abort the caller. */\nexport function safely(fn: () => void): void {\n try {\n fn();\n } catch {\n // best-effort; a failure here must never affect the surrounding operation\n }\n}\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\nconst SWEEP_MARKER_FILE = '.last-sweep';\nconst SWEEP_THROTTLE_MS = 24 * 60 * 60 * 1000; // once a day is enough given day-granularity retention\n\nfunction sweepOldLogs(logDir: string): void {\n safely(() => {\n const markerPath = path.join(logDir, SWEEP_MARKER_FILE);\n let shouldSweep = true;\n try {\n shouldSweep = Date.now() - statSync(markerPath).mtimeMs >= SWEEP_THROTTLE_MS;\n } catch {\n // no marker yet (first run in this directory) - sweep now\n }\n if (!shouldSweep) return;\n\n const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1000;\n for (const file of readdirSync(logDir)) {\n if (file === SWEEP_MARKER_FILE) continue;\n const filePath = path.join(logDir, file);\n if (statSync(filePath).mtimeMs < cutoffMs) {\n unlinkSync(filePath);\n }\n }\n writeFileSync(markerPath, '');\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 safely(() => logger.info({ command: commandName }, 'started'));\n\n try {\n await action(logger);\n safely(() => logger.info({ command: commandName }, 'completed'));\n if (process.env.DEBUG) {\n console.log(`Details: ${logFilePath}`);\n }\n } catch (error) {\n safely(() => logger.error({ command: commandName, err: error }, 'failed'));\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 { UserError } from '../errors';\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 UserError('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 // Reads + hashes run in parallel; results are then applied in CORE_FILE_PATHS\n // order below so files/fileWrites stay deterministic regardless of I/O timing.\n const perFileResults = await Promise.all(\n CORE_FILE_PATHS.map(async (relativePath) => {\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 return { relativePath, newContent, result };\n }),\n );\n\n for (const { relativePath, newContent, result } of perFileResults) {\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';\nconst FETCH_TIMEOUT_MS = 5000;\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, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\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","FETCH_TIMEOUT_MS","defaultRunCommand","spawn","readFile","path","mkdir","writeFile","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clispark",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "Interactive scaffolding tool for new CLI projects",
5
5
  "keywords": [
6
6
  "cli",
@@ -65,7 +65,20 @@ Every entry in a command's `static args` is built from `@oclif/core`'s `Args` he
65
65
  // `task <title> urgent` → "Expected urgent to be one of: low, medium, high"
66
66
  ```
67
67
 
68
- See `task.ts` (string + enum-constrained string), `task/complete.ts` (integer), and `task/list.ts` (string + boolean) for these in a real, runnable command.
68
+ See `task.ts` (string + enum-constrained string), `task/complete.ts` (integer), and `task/list.ts` (string arg + boolean flag) for these in a real, runnable command.
69
+
70
+ ## Flags
71
+
72
+ Flags (`--name`/`-n`) are `@oclif/core`'s other input mechanism, declared in `static flags` alongside (or instead of) `static args`. Unlike args, they're named and order-independent — the more common choice for optional inputs once a command has more than one or two of them.
73
+
74
+ - **`Flags.boolean()`** — presence-based: passing the flag sets it to `true`, omitting it leaves it `undefined`.
75
+ ```ts
76
+ done: Flags.boolean({ description: 'Only show completed tasks' })
77
+ // `task list --done` → done === true; omitted → done === undefined
78
+ ```
79
+ - Flags otherwise mirror the `Args` catalogue above (`Flags.string()`, `Flags.integer()`, `options: [...]`, etc.) — the choice between an arg and a flag is about calling convention (positional vs. named), not available types.
80
+
81
+ See `task/list.ts`'s `--done` flag for this in a real, runnable command.
69
82
 
70
83
  ## Command Discovery
71
84
 
@@ -16,11 +16,11 @@ Two example commands ship in `src/commands/` as copy-paste starting points:
16
16
  ```bash
17
17
  node bin/run.ts hello
18
18
  ```
19
- - **`task`** / **`task complete`** / **`task list`** (`src/commands/task.ts`, `src/commands/task/`) — a reference for oclif's argument and subcommand patterns: required args, optional args, an enum-constrained arg, an integer arg, a boolean arg, and how a folder under `src/commands/` becomes a subcommand.
19
+ - **`task`** / **`task complete`** / **`task list`** (`src/commands/task.ts`, `src/commands/task/`) — a reference for oclif's argument, flag, and subcommand patterns: required args, optional args, an enum-constrained arg, an integer arg, a boolean flag, and how a folder under `src/commands/` becomes a subcommand.
20
20
  ```bash
21
21
  node bin/run.ts task "Buy milk" high
22
22
  node bin/run.ts task complete 1
23
- node bin/run.ts task list groceries
23
+ node bin/run.ts task list groceries --done
24
24
  ```
25
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
26
 
@@ -1,7 +1,7 @@
1
1
  // templates/base/src/base-command.ts
2
2
  import { Command, type Interfaces } from '@oclif/core';
3
3
  import type { Logger } from 'pino';
4
- import { createLogger } from './logger';
4
+ import { createLogger, safely } from './logger';
5
5
 
6
6
  export abstract class BaseCommand extends Command {
7
7
  protected logger?: Logger;
@@ -13,19 +13,11 @@ export abstract class BaseCommand extends Command {
13
13
  const { logger, logFilePath } = createLogger(this.id ?? 'unknown');
14
14
  this.logger = logger;
15
15
  this.logFilePath = logFilePath;
16
- try {
17
- this.logger.info({ command: this.id }, 'started');
18
- } catch {
19
- // best-effort logging; a write failure here must not abort a command that hasn't run yet
20
- }
16
+ safely(() => this.logger?.info({ command: this.id }, 'started'));
21
17
  }
22
18
 
23
19
  protected async catch(err: Interfaces.CommandError): Promise<unknown> {
24
- try {
25
- this.logger?.error({ command: this.id, err }, 'failed');
26
- } catch {
27
- // best-effort logging; never let a log-write failure mask the real error
28
- }
20
+ safely(() => this.logger?.error({ command: this.id, err }, 'failed'));
29
21
  if (this.logFilePath) {
30
22
  console.error(`Details: ${this.logFilePath}`);
31
23
  }
@@ -34,11 +26,7 @@ export abstract class BaseCommand extends Command {
34
26
 
35
27
  protected async finally(err: Error | undefined): Promise<unknown> {
36
28
  if (!err) {
37
- try {
38
- this.logger?.info({ command: this.id }, 'completed');
39
- } catch {
40
- // best-effort logging; never let a log-write failure crash a successful run
41
- }
29
+ safely(() => this.logger?.info({ command: this.id }, 'completed'));
42
30
  if (process.env.DEBUG && this.logFilePath) {
43
31
  console.log(`Details: ${this.logFilePath}`);
44
32
  }
@@ -13,14 +13,14 @@ describe('task list', () => {
13
13
  expect(stdout).toContain('Listing tasks matching "groceries"');
14
14
  });
15
15
 
16
- it('combines a filter with the done flag', async () => {
17
- const { stdout } = await runCommand('task list groceries true');
16
+ it('combines a filter with the --done flag', async () => {
17
+ const { stdout } = await runCommand('task list groceries --done');
18
18
  expect(stdout).toContain('Listing tasks matching "groceries" (completed only: true)');
19
19
  });
20
20
 
21
- it('parses "no" as false for the done arg', async () => {
22
- const { stdout } = await runCommand('task list groceries no');
23
- expect(stdout).toContain('(completed only: false)');
21
+ it('omits the completed-only note when --done is not passed', async () => {
22
+ const { stdout } = await runCommand('task list groceries');
23
+ expect(stdout).not.toContain('completed only');
24
24
  });
25
25
 
26
26
  it('shows usage examples in --help', async () => {
@@ -28,6 +28,6 @@ describe('task list', () => {
28
28
  expect(stdout).toContain('EXAMPLES');
29
29
  expect(stdout).toContain('task list');
30
30
  expect(stdout).toContain('task list groceries');
31
- expect(stdout).toContain('task list groceries true');
31
+ expect(stdout).toContain('task list groceries --done');
32
32
  });
33
33
  });
@@ -1,9 +1,9 @@
1
1
  // templates/base/src/commands/task/list.ts
2
- import { Args } from '@oclif/core';
2
+ import { Args, Flags } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command';
4
4
 
5
5
  export default class TaskList extends BaseCommand {
6
- static description = 'List tasks (demonstrates a subcommand with two optional arguments of different types)';
6
+ static description = 'List tasks (demonstrates an optional argument combined with a boolean flag)';
7
7
  static examples = [
8
8
  {
9
9
  command: '<%= config.bin %> task list',
@@ -14,19 +14,20 @@ export default class TaskList extends BaseCommand {
14
14
  description: 'Lists tasks matching a filter term',
15
15
  },
16
16
  {
17
- command: '<%= config.bin %> task list groceries true',
17
+ command: '<%= config.bin %> task list groceries --done',
18
18
  description: 'Lists tasks matching a filter, showing only completed ones',
19
19
  },
20
20
  ];
21
21
  static args = {
22
22
  filter: Args.string({ required: false, description: 'Optional filter term' }),
23
- done: Args.boolean({ required: false, description: 'Only show completed tasks (true/false)' }),
24
23
  };
25
- static flags = {};
24
+ static flags = {
25
+ done: Flags.boolean({ description: 'Only show completed tasks' }),
26
+ };
26
27
 
27
28
  async run(): Promise<void> {
28
- const { args } = await this.parse(TaskList);
29
+ const { args, flags } = await this.parse(TaskList);
29
30
  const base = args.filter ? `Listing tasks matching "${args.filter}"` : 'Listing all tasks';
30
- this.log(args.done !== undefined ? `${base} (completed only: ${args.done})` : base);
31
+ this.log(flags.done ? `${base} (completed only: true)` : base);
31
32
  }
32
33
  }
@@ -7,6 +7,15 @@ import pino, { type Logger } from 'pino';
7
7
 
8
8
  const paths = envPaths('{{projectName}}', { suffix: '' });
9
9
 
10
+ /** Runs fn, silently swallowing any error — for best-effort work that must never abort the caller. */
11
+ export function safely(fn: () => void): void {
12
+ try {
13
+ fn();
14
+ } catch {
15
+ // best-effort; a failure here must never affect the surrounding operation
16
+ }
17
+ }
18
+
10
19
  export interface LoggerHandle {
11
20
  logger: Logger;
12
21
  logFilePath: string;
@@ -44,13 +53,15 @@ const SWEEP_MARKER_FILE = '.last-sweep';
44
53
  const SWEEP_THROTTLE_MS = 24 * 60 * 60 * 1000; // once a day is enough given day-granularity retention
45
54
 
46
55
  function sweepOldLogs(logDir: string): void {
47
- try {
56
+ safely(() => {
48
57
  const markerPath = path.join(logDir, SWEEP_MARKER_FILE);
58
+ let shouldSweep = true;
49
59
  try {
50
- if (Date.now() - statSync(markerPath).mtimeMs < SWEEP_THROTTLE_MS) return;
60
+ shouldSweep = Date.now() - statSync(markerPath).mtimeMs >= SWEEP_THROTTLE_MS;
51
61
  } catch {
52
62
  // no marker yet (first run in this directory) - sweep now
53
63
  }
64
+ if (!shouldSweep) return;
54
65
 
55
66
  const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1000;
56
67
  for (const file of readdirSync(logDir)) {
@@ -61,9 +72,7 @@ function sweepOldLogs(logDir: string): void {
61
72
  }
62
73
  }
63
74
  writeFileSync(markerPath, '');
64
- } catch {
65
- // best-effort cleanup; a broken sweep must never block the actual command
66
- }
75
+ });
67
76
  }
68
77
 
69
78
  export function createLogger(commandName: string, logDir: string = paths.log): LoggerHandle {