@precisa-saude/cli 1.0.1

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.
Files changed (34) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +131 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +640 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/index.d.ts +86 -0
  7. package/dist/index.js +630 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/templates/README.md +51 -0
  10. package/dist/templates/dotfiles/editorconfig +12 -0
  11. package/dist/templates/dotfiles/gitignore +10 -0
  12. package/dist/templates/dotfiles/npmrc +1 -0
  13. package/dist/templates/dotfiles/nvmrc +1 -0
  14. package/dist/templates/dotfiles/prettierignore +7 -0
  15. package/dist/templates/dotfiles/prettierrc +1 -0
  16. package/dist/templates/dotfiles/renovate.json +29 -0
  17. package/dist/templates/github/ISSUE_TEMPLATE/bug.md +33 -0
  18. package/dist/templates/github/ISSUE_TEMPLATE/config.yml +8 -0
  19. package/dist/templates/github/ISSUE_TEMPLATE/feature.md +18 -0
  20. package/dist/templates/github/PULL_REQUEST_TEMPLATE.md +18 -0
  21. package/dist/templates/github/workflows/ci.yml +44 -0
  22. package/dist/templates/github/workflows/release.yml +62 -0
  23. package/dist/templates/github/workflows/review.yml +435 -0
  24. package/dist/templates/governance/CITATION.cff +8 -0
  25. package/dist/templates/governance/CODE_OF_CONDUCT.md +43 -0
  26. package/dist/templates/governance/CONTRIBUTING.md +32 -0
  27. package/dist/templates/governance/CONVENTIONS.md +37 -0
  28. package/dist/templates/governance/SECURITY.md +22 -0
  29. package/dist/templates/governance/SUPPORT.md +14 -0
  30. package/dist/templates/husky/commit-msg +8 -0
  31. package/dist/templates/husky/pre-commit +1 -0
  32. package/dist/templates/husky/pre-push +21 -0
  33. package/dist/templates/templates.manifest.yml +125 -0
  34. package/package.json +62 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/doctor.ts","../src/lib/templates.ts","../src/lib/paths.ts","../src/manifest.ts","../src/commands/new.ts","../src/lib/merge.ts","../src/commands/sync.ts","../src/lib/diff.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport chalk from 'chalk';\n\nimport {\n loadTemplateManifest,\n readTemplateSource,\n renderTokens,\n type TemplateEntry,\n} from '../lib/templates.js';\nimport { isRequired, loadManifest, tokenContext } from '../manifest.js';\n\ntype Severity = 'ok' | 'info' | 'warning' | 'error';\n\ninterface DriftReport {\n message: string;\n severity: Severity;\n target: string;\n}\n\nexport async function runDoctor(): Promise<void> {\n const cwd = process.cwd();\n console.log(chalk.bold.cyan('\\nprecisa doctor'));\n\n let manifest;\n try {\n manifest = loadManifest(cwd);\n } catch (err) {\n console.error(chalk.red(`\\n${(err as Error).message}`));\n process.exit(1);\n }\n\n const entries = loadTemplateManifest();\n const context = tokenContext(manifest);\n\n const reports: DriftReport[] = [];\n\n for (const entry of entries) {\n const required = isRequired(entry.required_when, manifest);\n const targetPath = resolve(cwd, entry.target);\n const exists = existsSync(targetPath);\n\n if (!required && !exists) continue;\n if (!required && exists) {\n reports.push({\n message: `Present but not required by the manifest profile (ok)`,\n severity: 'info',\n target: entry.target,\n });\n continue;\n }\n if (!exists) {\n reports.push({\n message: 'Missing',\n severity: 'error',\n target: entry.target,\n });\n continue;\n }\n\n // Exists + required — compare to rendered template\n const source = readTemplateSource(entry);\n const rendered = renderTokens(source, context);\n const current = readFileSync(targetPath, 'utf-8');\n reports.push(compareContent(entry, current, rendered));\n }\n\n console.log('');\n let errors = 0;\n let warnings = 0;\n let infos = 0;\n let oks = 0;\n\n for (const r of reports) {\n const icon = iconFor(r.severity);\n const line = `${icon} ${r.target}${r.message ? chalk.dim(` — ${r.message}`) : ''}`;\n console.log(line);\n if (r.severity === 'error') errors += 1;\n else if (r.severity === 'warning') warnings += 1;\n else if (r.severity === 'info') infos += 1;\n else oks += 1;\n }\n\n console.log('');\n console.log(chalk.dim(`${oks} ok, ${warnings} warning, ${errors} error, ${infos} info`));\n\n if (errors > 0) process.exit(1);\n}\n\nfunction compareContent(entry: TemplateEntry, current: string, rendered: string): DriftReport {\n if (entry.merge_strategy === 'preserve') {\n return {\n message:\n current === rendered ? '' : 'Differs from template (preserve strategy — suggestion only)',\n severity: current === rendered ? 'ok' : 'info',\n target: entry.target,\n };\n }\n if (current === rendered) {\n return { message: '', severity: 'ok', target: entry.target };\n }\n return {\n message: 'Differs from template (run `precisa sync` to update)',\n severity: 'warning',\n target: entry.target,\n };\n}\n\nfunction iconFor(severity: Severity): string {\n switch (severity) {\n case 'ok':\n return chalk.green('✓');\n case 'info':\n return chalk.blue('i');\n case 'warning':\n return chalk.yellow('!');\n case 'error':\n return chalk.red('✗');\n }\n}\n","import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { parse as parseYaml } from 'yaml';\n\nimport type { RequiredWhen } from '../manifest.js';\nimport { resolveTemplatesDir } from './paths.js';\n\nexport type MergeStrategy = 'overwrite' | 'merge_json' | 'preserve' | 'skip_if_exists';\n\nexport interface TemplateEntry {\n /** Whether to mark the rendered file executable (chmod +x). */\n executable?: boolean;\n /** How to merge when the target already exists. */\n merge_strategy: MergeStrategy;\n /** Gate for inclusion. */\n required_when: RequiredWhen;\n /** Path relative to templates/. */\n source: string;\n /** Path in the target repo (relative to the repo root). */\n target: string;\n}\n\ninterface TemplateManifestFile {\n templates: TemplateEntry[];\n}\n\n/**\n * Load the `templates.manifest.yml` from the bundled templates directory.\n * Throws if the file is missing or the shape is wrong.\n */\nexport function loadTemplateManifest(): TemplateEntry[] {\n const dir = resolveTemplatesDir();\n const manifestPath = resolve(dir, 'templates.manifest.yml');\n const raw = readFileSync(manifestPath, 'utf-8');\n const parsed = parseYaml(raw) as TemplateManifestFile | undefined;\n if (!parsed || !Array.isArray(parsed.templates)) {\n throw new Error(`${manifestPath}: expected { templates: [...] } at root`);\n }\n for (const [i, t] of parsed.templates.entries()) {\n if (!t.source || !t.target) {\n throw new Error(`${manifestPath}: entry ${i} is missing source or target`);\n }\n }\n return parsed.templates;\n}\n\nexport function readTemplateSource(entry: TemplateEntry): string {\n const dir = resolveTemplatesDir();\n const path = resolve(dir, entry.source);\n return readFileSync(path, 'utf-8');\n}\n\n/**\n * Substitute `{{TOKEN}}` placeholders with values from `context`. Unknown\n * tokens are left untouched (keeps the double braces visible so bad keys\n * are easy to spot in rendered output).\n */\nexport function renderTokens(source: string, context: Record<string, string>): string {\n return source.replace(/\\{\\{([A-Z_]+)\\}\\}/g, (match, token: string) => {\n return Object.prototype.hasOwnProperty.call(context, token) ? context[token]! : match;\n });\n}\n","import { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Locate the bundled `templates/` directory.\n *\n * When published to npm, the templates live at `dist/templates/` inside\n * the CLI package (copied by `scripts/copy-templates.mjs` during build).\n *\n * When running from the monorepo (e.g. `pnpm --filter @precisa-saude/cli dev`),\n * the templates live at the repo root — fall back to walking up from the\n * package directory.\n */\nexport function resolveTemplatesDir(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n\n // Published case: cli package's dist/templates\n const bundled = resolve(here, 'templates');\n if (existsSync(bundled)) return bundled;\n\n // Dev case: walk up from packages/cli/{src|dist} to the monorepo root\n const repoRoot = resolve(here, '..', '..', '..', '..');\n const repoTemplates = resolve(repoRoot, 'templates');\n if (existsSync(repoTemplates)) return repoTemplates;\n\n // Sibling-in-package case (tsup output): packages/cli/dist -> ../../templates\n const packageParent = resolve(here, '..', '..', 'templates');\n if (existsSync(packageParent)) return packageParent;\n\n throw new Error(\n `Could not locate templates/ directory. Searched:\\n ${bundled}\\n ${repoTemplates}\\n ${packageParent}`,\n );\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\n/**\n * `.precisa.json` manifest — sits at the root of every consumer repo and\n * declares the repo's profile so `precisa sync` and `precisa doctor` know\n * which templates to render and which rules to enforce.\n */\nexport interface PrecisaManifest {\n /** Commitlint scope enum for this repo. */\n commitScopes: string[];\n\n /** Contact emails used in governance doc templates. */\n contactEmails: {\n security: string;\n conduct: string;\n };\n\n /** Does this repo have publishable packages/*? */\n hasPackages: boolean;\n\n /** Does this repo ship a website? Controls preview-deploy workflows. */\n hasSite: boolean;\n\n /** Repository name (typically matches the GitHub repo name). */\n name: string;\n\n /** Pinned runtime versions. Default: node=22, pnpm=9.15.9. */\n nodeVersion?: string;\n\n /** GitHub organization or user that owns the repo (e.g. `Precisa-Saude`). */\n owner: string;\n\n pnpmVersion?: string;\n\n /** Publishes any workspace package to npm? */\n publishesToNpm: boolean;\n\n /** Schema version of this manifest file. Bump when the schema changes. */\n schemaVersion: 1;\n /** Public-OSS or private-internal. Controls which templates are rendered. */\n visibility: 'oss' | 'private';\n}\n\nexport const MANIFEST_FILENAME = '.precisa.json';\n\nexport const DEFAULT_MANIFEST_FIELDS = {\n hasPackages: true,\n hasSite: false,\n nodeVersion: '22',\n pnpmVersion: '9.15.9',\n publishesToNpm: true,\n schemaVersion: 1 as const,\n visibility: 'oss' as const,\n};\n\n/** Template gate values — `required_when` in templates.manifest.yml. */\nexport type RequiredWhen =\n | 'always'\n | 'oss'\n | 'private'\n | 'has_site'\n | 'has_packages'\n | 'publishes_to_npm';\n\n/** Returns true when a template's `required_when` gate applies to this manifest. */\nexport function isRequired(when: RequiredWhen, manifest: PrecisaManifest): boolean {\n switch (when) {\n case 'always':\n return true;\n case 'oss':\n return manifest.visibility === 'oss';\n case 'private':\n return manifest.visibility === 'private';\n case 'has_site':\n return manifest.hasSite;\n case 'has_packages':\n return manifest.hasPackages;\n case 'publishes_to_npm':\n return manifest.publishesToNpm;\n default:\n return true;\n }\n}\n\n/**\n * Build the token substitution map from a manifest. Keys are `{{TOKEN}}`\n * (without braces); values are always strings.\n */\nexport function tokenContext(manifest: PrecisaManifest): Record<string, string> {\n return {\n COMMIT_SCOPES: manifest.commitScopes.join(','),\n CONDUCT_EMAIL: manifest.contactEmails.conduct,\n HAS_PACKAGES: String(manifest.hasPackages),\n HAS_SITE: String(manifest.hasSite),\n NODE_VERSION: manifest.nodeVersion ?? '22',\n OWNER_ORG: manifest.owner,\n PNPM_VERSION: manifest.pnpmVersion ?? '9.15.9',\n PUBLISHES_TO_NPM: String(manifest.publishesToNpm),\n REPO_NAME: manifest.name,\n REPO_SLUG: `${manifest.owner}/${manifest.name}`,\n SECURITY_EMAIL: manifest.contactEmails.security,\n VISIBILITY: manifest.visibility,\n };\n}\n\nexport interface ManifestValidationError {\n message: string;\n path: string;\n}\n\nexport function validateManifest(raw: unknown): ManifestValidationError[] {\n const errors: ManifestValidationError[] = [];\n const m = raw as Partial<PrecisaManifest> | undefined;\n if (!m || typeof m !== 'object') {\n return [{ message: 'manifest must be a JSON object', path: '$' }];\n }\n if (m.schemaVersion !== 1) {\n errors.push({ message: 'must be 1', path: 'schemaVersion' });\n }\n if (typeof m.name !== 'string' || !m.name) {\n errors.push({ message: 'must be a non-empty string', path: 'name' });\n }\n if (typeof m.owner !== 'string' || !m.owner) {\n errors.push({ message: 'must be a non-empty string', path: 'owner' });\n }\n if (m.visibility !== 'oss' && m.visibility !== 'private') {\n errors.push({ message: \"must be 'oss' or 'private'\", path: 'visibility' });\n }\n for (const key of ['hasSite', 'hasPackages', 'publishesToNpm'] as const) {\n if (typeof m[key] !== 'boolean') {\n errors.push({ message: 'must be a boolean', path: key });\n }\n }\n if (!Array.isArray(m.commitScopes)) {\n errors.push({ message: 'must be an array of strings', path: 'commitScopes' });\n }\n if (!m.contactEmails || typeof m.contactEmails !== 'object') {\n errors.push({ message: 'must be an object', path: 'contactEmails' });\n } else {\n for (const key of ['security', 'conduct'] as const) {\n if (typeof m.contactEmails[key] !== 'string' || !m.contactEmails[key]) {\n errors.push({\n message: 'must be a non-empty string',\n path: `contactEmails.${key}`,\n });\n }\n }\n }\n return errors;\n}\n\nexport function loadManifest(cwd: string): PrecisaManifest {\n const path = resolve(cwd, MANIFEST_FILENAME);\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, 'utf-8'));\n } catch (err) {\n throw new Error(`Failed to read ${MANIFEST_FILENAME} at ${path}: ${(err as Error).message}`);\n }\n const errors = validateManifest(raw);\n if (errors.length > 0) {\n const detail = errors.map((e) => ` - ${e.path}: ${e.message}`).join('\\n');\n throw new Error(`Invalid ${MANIFEST_FILENAME}:\\n${detail}`);\n }\n return raw as PrecisaManifest;\n}\n\nexport function writeManifest(cwd: string, manifest: PrecisaManifest): void {\n const path = resolve(cwd, MANIFEST_FILENAME);\n writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\\n`);\n}\n","import { execSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport prompts from 'prompts';\n\nimport { applyTemplate } from '../lib/merge.js';\nimport { loadTemplateManifest, readTemplateSource, renderTokens } from '../lib/templates.js';\nimport {\n DEFAULT_MANIFEST_FIELDS,\n isRequired,\n type PrecisaManifest,\n tokenContext,\n writeManifest,\n} from '../manifest.js';\n\nexport interface NewOptions {\n dryRun: boolean;\n profile: string;\n}\n\nconst PROFILES = {\n 'oss-library': {\n hasPackages: true,\n hasSite: false,\n publishesToNpm: true,\n visibility: 'oss',\n },\n 'oss-site': {\n hasPackages: false,\n hasSite: true,\n publishesToNpm: false,\n visibility: 'oss',\n },\n 'private-app': {\n hasPackages: true,\n hasSite: true,\n publishesToNpm: false,\n visibility: 'private',\n },\n} as const satisfies Record<\n string,\n Pick<PrecisaManifest, 'visibility' | 'hasSite' | 'hasPackages' | 'publishesToNpm'>\n>;\n\nexport async function runNew(repoName: string, opts: NewOptions): Promise<void> {\n const targetDir = resolve(process.cwd(), repoName);\n\n if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {\n console.error(chalk.red(`\\nError: ${targetDir} already exists and is not empty.`));\n process.exit(1);\n }\n\n const profile = PROFILES[opts.profile as keyof typeof PROFILES];\n if (!profile) {\n console.error(\n chalk.red(`\\nError: unknown profile '${opts.profile}'. Choose one of:`),\n Object.keys(PROFILES).join(', '),\n );\n process.exit(1);\n }\n\n console.log(chalk.bold.cyan(`\\nprecisa new ${repoName}`));\n console.log(chalk.dim(`profile: ${opts.profile}${opts.dryRun ? ' (dry-run)' : ''}`));\n console.log(chalk.dim(`target: ${targetDir}\\n`));\n\n const answers = await prompts(\n [\n {\n initial: 'Precisa-Saude',\n message: 'GitHub owner (org or user):',\n name: 'owner',\n type: 'text',\n },\n {\n initial: 'security@precisa-saude.com.br',\n message: 'Security contact email:',\n name: 'securityEmail',\n type: 'text',\n },\n {\n initial: 'conduct@precisa-saude.com.br',\n message: 'Code-of-conduct contact email:',\n name: 'conductEmail',\n type: 'text',\n },\n {\n initial: 'docs,ci,deps',\n message: 'Commit scopes (comma-separated):',\n name: 'commitScopes',\n separator: ',',\n type: 'list',\n },\n ],\n { onCancel: () => process.exit(1) },\n );\n\n const manifest: PrecisaManifest = {\n ...DEFAULT_MANIFEST_FIELDS,\n ...profile,\n commitScopes: answers.commitScopes,\n contactEmails: {\n conduct: answers.conductEmail,\n security: answers.securityEmail,\n },\n name: repoName,\n owner: answers.owner,\n };\n\n const spinner = ora('Rendering templates').start();\n try {\n if (!opts.dryRun) mkdirSync(targetDir, { recursive: true });\n\n const entries = loadTemplateManifest().filter((e) => isRequired(e.required_when, manifest));\n const context = tokenContext(manifest);\n\n let created = 0;\n let skipped = 0;\n\n for (const entry of entries) {\n const source = readTemplateSource(entry);\n const rendered = renderTokens(source, context);\n const outcome = applyTemplate({\n cwd: targetDir,\n dryRun: opts.dryRun,\n entry,\n rendered,\n });\n if (outcome.kind === 'create') created += 1;\n else if (outcome.kind === 'skip-exists') skipped += 1;\n else if (outcome.kind === 'error') {\n spinner.fail(`Failed on ${entry.target}`);\n console.error(chalk.red(outcome.message));\n process.exit(1);\n }\n }\n\n spinner.succeed(\n opts.dryRun\n ? `Would render ${created} file${created === 1 ? '' : 's'} (${skipped} skipped)`\n : `Rendered ${created} file${created === 1 ? '' : 's'} (${skipped} skipped)`,\n );\n\n if (opts.dryRun) return;\n\n writeManifest(targetDir, manifest);\n console.log(chalk.dim(` wrote .precisa.json`));\n\n const gitStep = ora('git init + pnpm install').start();\n try {\n execSync('git init --initial-branch=main', { cwd: targetDir, stdio: 'pipe' });\n execSync('pnpm install', { cwd: targetDir, stdio: 'pipe' });\n gitStep.succeed('git init + pnpm install complete');\n } catch (err) {\n gitStep.warn(\n `git/pnpm setup skipped: ${(err as Error).message}. Run manually in ${targetDir}.`,\n );\n }\n\n console.log(chalk.bold.green(`\\n✓ ${repoName} scaffolded at ${targetDir}`));\n console.log(chalk.dim('\\nNext:'));\n console.log(chalk.dim(` cd ${repoName}`));\n console.log(chalk.dim(` # start adding your code`));\n } catch (err) {\n spinner.fail((err as Error).message);\n process.exit(1);\n }\n}\n","import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\n\nimport type { MergeStrategy, TemplateEntry } from './templates.js';\n\nexport type ApplyOutcome =\n | { kind: 'create'; target: string; rendered: string }\n | { kind: 'update'; target: string; previous: string; rendered: string }\n | { kind: 'skip-identical'; target: string }\n | { kind: 'preserve'; target: string; current: string; rendered: string }\n | { kind: 'skip-exists'; target: string }\n | { kind: 'merge-json'; target: string; previous: string; rendered: string }\n | { kind: 'error'; target: string; message: string };\n\nexport interface ApplyOptions {\n /** Repo root to apply into. */\n cwd: string;\n /** When true, compute the outcome but do not write to disk. */\n dryRun: boolean;\n /** Template entry being applied. */\n entry: TemplateEntry;\n /** Rendered template content (post token substitution). */\n rendered: string;\n}\n\n/**\n * Apply a single template file to the target repo. Returns the outcome\n * so callers can print a per-file status row and, on dry-run, a diff.\n */\nexport function applyTemplate({ cwd, dryRun, entry, rendered }: ApplyOptions): ApplyOutcome {\n const targetPath = resolve(cwd, entry.target);\n const exists = existsSync(targetPath);\n\n if (!exists) {\n if (!dryRun) writeFile(targetPath, rendered, entry.executable === true);\n return { kind: 'create', rendered, target: entry.target };\n }\n\n const current = readFileSync(targetPath, 'utf-8');\n\n switch (entry.merge_strategy as MergeStrategy) {\n case 'overwrite': {\n if (current === rendered) {\n return { kind: 'skip-identical', target: entry.target };\n }\n if (!dryRun) writeFile(targetPath, rendered, entry.executable === true);\n return { kind: 'update', previous: current, rendered, target: entry.target };\n }\n case 'skip_if_exists': {\n return { kind: 'skip-exists', target: entry.target };\n }\n case 'preserve': {\n return { current, kind: 'preserve', rendered, target: entry.target };\n }\n case 'merge_json': {\n let merged: string;\n try {\n merged = mergeJson(current, rendered);\n } catch (err) {\n return {\n kind: 'error',\n message: `JSON merge failed: ${(err as Error).message}`,\n target: entry.target,\n };\n }\n if (current === merged) {\n return { kind: 'skip-identical', target: entry.target };\n }\n if (!dryRun) writeFile(targetPath, merged, entry.executable === true);\n return { kind: 'merge-json', previous: current, rendered: merged, target: entry.target };\n }\n default: {\n return {\n kind: 'error',\n message: `Unknown merge_strategy '${entry.merge_strategy}'`,\n target: entry.target,\n };\n }\n }\n}\n\nfunction writeFile(path: string, contents: string, executable: boolean): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, contents);\n if (executable) chmodSync(path, 0o755);\n}\n\n/**\n * Shallow 3-way-ish JSON merge — the current file wins on conflicts so\n * repo-local customizations aren't clobbered. Objects are merged\n * key-by-key; arrays and scalars are replaced entirely by the current\n * value when present (falls back to the template value).\n *\n * If parsing fails, throws so the caller can report the error.\n */\nexport function mergeJson(currentRaw: string, templateRaw: string): string {\n const current = JSON.parse(currentRaw) as unknown;\n const template = JSON.parse(templateRaw) as unknown;\n const merged = mergeValue(template, current);\n return `${JSON.stringify(merged, null, 2)}\\n`;\n}\n\nfunction mergeValue(template: unknown, current: unknown): unknown {\n if (current === undefined) return template;\n if (\n typeof template !== 'object' ||\n template === null ||\n Array.isArray(template) ||\n typeof current !== 'object' ||\n current === null ||\n Array.isArray(current)\n ) {\n // Scalars / arrays: current value wins (never clobber repo-local values).\n return current;\n }\n const out: Record<string, unknown> = {};\n const keys = new Set([\n ...Object.keys(template as Record<string, unknown>),\n ...Object.keys(current as Record<string, unknown>),\n ]);\n for (const k of keys) {\n out[k] = mergeValue(\n (template as Record<string, unknown>)[k],\n (current as Record<string, unknown>)[k],\n );\n }\n return out;\n}\n","import chalk from 'chalk';\n\nimport { colorDiff } from '../lib/diff.js';\nimport { type ApplyOutcome, applyTemplate } from '../lib/merge.js';\nimport { loadTemplateManifest, readTemplateSource, renderTokens } from '../lib/templates.js';\nimport { isRequired, loadManifest, tokenContext } from '../manifest.js';\n\nexport interface SyncOptions {\n dryRun: boolean;\n}\n\nexport async function runSync(opts: SyncOptions): Promise<void> {\n const cwd = process.cwd();\n console.log(chalk.bold.cyan(`\\nprecisa sync${opts.dryRun ? ' --dry-run' : ''}`));\n\n let manifest;\n try {\n manifest = loadManifest(cwd);\n } catch (err) {\n console.error(chalk.red(`\\n${(err as Error).message}`));\n console.error(\n chalk.dim('\\n Run `precisa new <repo>` in an empty directory to scaffold a new repo,'),\n );\n console.error(chalk.dim(' or create a `.precisa.json` manifest manually.'));\n process.exit(1);\n }\n\n const entries = loadTemplateManifest().filter((e) => isRequired(e.required_when, manifest));\n const context = tokenContext(manifest);\n\n const outcomes: ApplyOutcome[] = [];\n for (const entry of entries) {\n const source = readTemplateSource(entry);\n const rendered = renderTokens(source, context);\n outcomes.push(\n applyTemplate({\n cwd,\n dryRun: opts.dryRun,\n entry,\n rendered,\n }),\n );\n }\n\n console.log('');\n for (const o of outcomes) {\n printOutcome(o, opts.dryRun);\n }\n\n const summary = summarize(outcomes);\n console.log('');\n console.log(\n chalk.dim(\n `${summary.create} create, ${summary.update} update, ${summary.mergeJson} merge-json, ${summary.preserve} preserve, ${summary.skip} skip, ${summary.error} error`,\n ),\n );\n\n if (summary.error > 0) process.exit(1);\n}\n\ninterface Summary {\n create: number;\n error: number;\n mergeJson: number;\n preserve: number;\n skip: number;\n update: number;\n}\n\nfunction summarize(outcomes: ApplyOutcome[]): Summary {\n const s: Summary = { create: 0, error: 0, mergeJson: 0, preserve: 0, skip: 0, update: 0 };\n for (const o of outcomes) {\n switch (o.kind) {\n case 'create':\n s.create += 1;\n break;\n case 'update':\n s.update += 1;\n break;\n case 'merge-json':\n s.mergeJson += 1;\n break;\n case 'preserve':\n s.preserve += 1;\n break;\n case 'skip-identical':\n case 'skip-exists':\n s.skip += 1;\n break;\n case 'error':\n s.error += 1;\n break;\n }\n }\n return s;\n}\n\nfunction printOutcome(o: ApplyOutcome, dryRun: boolean): void {\n const prefix = dryRun ? '(dry-run) ' : '';\n switch (o.kind) {\n case 'create':\n console.log(`${chalk.green('+')} ${prefix}create ${o.target}`);\n return;\n case 'update':\n console.log(`${chalk.yellow('~')} ${prefix}update ${o.target}`);\n if (dryRun) console.log(indent(colorDiff(o.target, o.previous, o.rendered)));\n return;\n case 'merge-json':\n console.log(`${chalk.yellow('~')} ${prefix}merge ${o.target}`);\n if (dryRun) console.log(indent(colorDiff(o.target, o.previous, o.rendered)));\n return;\n case 'preserve':\n console.log(\n `${chalk.blue('i')} ${prefix}preserve ${o.target} (template differs; not written)`,\n );\n if (dryRun && o.current !== o.rendered) {\n console.log(indent(colorDiff(o.target, o.current, o.rendered)));\n }\n return;\n case 'skip-identical':\n console.log(`${chalk.dim('=')} ${prefix}unchanged ${o.target}`);\n return;\n case 'skip-exists':\n console.log(`${chalk.dim('s')} ${prefix}skip ${o.target} (already exists)`);\n return;\n case 'error':\n console.log(`${chalk.red('!')} ${prefix}error ${o.target}: ${o.message}`);\n return;\n }\n}\n\nfunction indent(text: string): string {\n return text\n .split('\\n')\n .map((l) => ` ${l}`)\n .join('\\n');\n}\n","import chalk from 'chalk';\nimport { createPatch } from 'diff';\n\n/**\n * Format a unified diff with ANSI colors for terminal display.\n * Skips the `@@ hunk @@` metadata lines for a cleaner look.\n */\nexport function colorDiff(filename: string, previous: string, next: string): string {\n const patch = createPatch(filename, previous, next, '', '');\n const lines = patch.split('\\n');\n const out: string[] = [];\n for (const line of lines) {\n if (line.startsWith('---') || line.startsWith('+++')) continue;\n if (line.startsWith('@@')) {\n out.push(chalk.cyan(line));\n continue;\n }\n if (line.startsWith('+')) {\n out.push(chalk.green(line));\n continue;\n }\n if (line.startsWith('-')) {\n out.push(chalk.red(line));\n continue;\n }\n out.push(line);\n }\n return out.join('\\n');\n}\n"],"mappings":"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","names":["existsSync","readFileSync","resolve","resolve","resolve","readFileSync","resolve","resolve","existsSync","readFileSync","existsSync","mkdirSync","resolve","chalk","existsSync","readFileSync","writeFileSync","dirname","resolve","resolve","existsSync","chalk","mkdirSync","chalk","chalk","chalk"]}
@@ -0,0 +1,51 @@
1
+ # Templates
2
+
3
+ Files rendered into consumer repositories by `@precisa-saude/cli`.
4
+
5
+ ## Layout
6
+
7
+ Template source paths mirror the target-repo path (e.g. `templates/.github/workflows/ci.yml` lands at `<repo>/.github/workflows/ci.yml`). A single `templates.manifest.yml` at the root of this directory describes every file: what it renders to, when it's required, and how it merges with existing content.
8
+
9
+ ## Placeholders
10
+
11
+ Templates use mustache-style tokens. Resolved against the consumer repo's `.precisa.json` manifest at render time.
12
+
13
+ | Token | Source | Example |
14
+ | ---------------------- | -------------------------------------- | ------------------------------- |
15
+ | `{{REPO_NAME}}` | manifest `name` / directory name | `fhir-brasil` |
16
+ | `{{REPO_SLUG}}` | GitHub `owner/name` | `Precisa-Saude/fhir-brasil` |
17
+ | `{{OWNER_ORG}}` | GitHub org | `Precisa-Saude` |
18
+ | `{{VISIBILITY}}` | manifest `visibility` | `oss` / `private` |
19
+ | `{{NODE_VERSION}}` | manifest `nodeVersion` | `22` |
20
+ | `{{PNPM_VERSION}}` | manifest `pnpmVersion` | `9.15.9` |
21
+ | `{{SECURITY_EMAIL}}` | manifest `contactEmails.security` | `security@precisa-saude.com.br` |
22
+ | `{{CONDUCT_EMAIL}}` | manifest `contactEmails.conduct` | `conduct@precisa-saude.com.br` |
23
+ | `{{COMMIT_SCOPES}}` | manifest `commitScopes` (comma-joined) | `core,docs,ci,deps` |
24
+ | `{{HAS_SITE}}` | manifest `hasSite` | `true` / `false` |
25
+ | `{{HAS_PACKAGES}}` | manifest `hasPackages` | `true` / `false` |
26
+ | `{{PUBLISHES_TO_NPM}}` | manifest `publishesToNpm` | `true` / `false` |
27
+
28
+ Conditional sections use `{{#if KEY}}...{{/if}}` and `{{#unless KEY}}...{{/unless}}`.
29
+
30
+ ## Manifest schema
31
+
32
+ Each template is declared in `templates.manifest.yml`:
33
+
34
+ ```yaml
35
+ - source: path/under/templates/
36
+ target: path/in/consumer/repo
37
+ required_when: always | oss | private | has_site | has_packages | publishes_to_npm
38
+ merge_strategy: overwrite | merge_json | preserve | skip_if_exists
39
+ executable: false # optional; set true for shell scripts
40
+ ```
41
+
42
+ `merge_strategy` values:
43
+
44
+ - **overwrite** — always replace the target with the rendered template
45
+ - **merge_json** — three-way merge for JSON files (template base → current → new template); preserves repo-local keys
46
+ - **preserve** — never overwrite; `sync` emits the rendered diff as a suggestion
47
+ - **skip_if_exists** — create only if the target doesn't exist; subsequent runs leave it alone
48
+
49
+ ## CLI integration
50
+
51
+ `@precisa-saude/cli` (stub; full implementation pending) reads this manifest and renders templates against `.precisa.json`. See the `cli/` package for the rendering contract.
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ indent_style = space
7
+ indent_size = 2
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
@@ -0,0 +1,10 @@
1
+ node_modules
2
+ dist
3
+ build
4
+ coverage
5
+ .turbo
6
+ .DS_Store
7
+ *.log
8
+ .env
9
+ .env.local
10
+ .env.*.local
@@ -0,0 +1 @@
1
+ shamefully-hoist=false
@@ -0,0 +1 @@
1
+ {{NODE_VERSION}}
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ dist
3
+ build
4
+ coverage
5
+ .turbo
6
+ pnpm-lock.yaml
7
+ CHANGELOG.md
@@ -0,0 +1 @@
1
+ "@precisa-saude/prettier-config"
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": ["config:recommended", ":dependencyDashboard", ":semanticCommits"],
4
+ "schedule": ["before 6am on saturday"],
5
+ "timezone": "America/Sao_Paulo",
6
+ "labels": ["dependencies"],
7
+ "prConcurrentLimit": 5,
8
+ "packageRules": [
9
+ {
10
+ "matchUpdateTypes": ["minor", "patch"],
11
+ "matchDepTypes": ["devDependencies"],
12
+ "automerge": true,
13
+ "automergeType": "pr"
14
+ },
15
+ {
16
+ "matchUpdateTypes": ["major"],
17
+ "automerge": false,
18
+ "labels": ["dependencies", "major-update"]
19
+ },
20
+ {
21
+ "matchPackagePatterns": ["^@precisa-saude/"],
22
+ "groupName": "precisa-saude packages"
23
+ }
24
+ ],
25
+ "vulnerabilityAlerts": {
26
+ "labels": ["security"],
27
+ "automerge": true
28
+ }
29
+ }
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something is broken
4
+ title: '[bug] '
5
+ labels: [bug]
6
+ ---
7
+
8
+ ## What happened
9
+
10
+ <!-- Short description of the unexpected behavior. -->
11
+
12
+ ## Reproduction
13
+
14
+ <!-- Minimal steps. Include the exact command run, package + version, Node/pnpm versions. -->
15
+
16
+ ```bash
17
+ # command(s)
18
+ ```
19
+
20
+ ## Expected
21
+
22
+ <!-- What you expected to happen. -->
23
+
24
+ ## Environment
25
+
26
+ - Package + version:
27
+ - Node: `node --version`
28
+ - pnpm: `pnpm --version`
29
+ - OS: macOS / Linux / Windows
30
+
31
+ ## Additional context
32
+
33
+ <!-- Stack trace, CI logs, etc. -->
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: 'mailto:{{SECURITY_EMAIL}}'
5
+ about: Report security issues privately — see SECURITY.md
6
+ - name: General questions and discussions
7
+ url: 'https://github.com/{{REPO_SLUG}}/discussions'
8
+ about: Ask questions, share ideas, or discuss
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: Feature request
3
+ about: Propose a new feature or improvement
4
+ title: '[feat] '
5
+ labels: [enhancement]
6
+ ---
7
+
8
+ ## Problem
9
+
10
+ <!-- What limitation or friction are you hitting? -->
11
+
12
+ ## Proposed solution
13
+
14
+ <!-- Sketch the API / behavior. Prefer concrete examples over prose. -->
15
+
16
+ ## Alternatives considered
17
+
18
+ <!-- Other approaches you thought about and why you ruled them out. -->
@@ -0,0 +1,18 @@
1
+ <!-- Thanks for contributing. Please fill out the sections below. -->
2
+
3
+ ## Summary
4
+
5
+ <!-- One or two sentences on what this changes and why. -->
6
+
7
+ ## Type of change
8
+
9
+ - [ ] Bug fix (patch)
10
+ - [ ] Feature (minor)
11
+ - [ ] Breaking change (major — include a `BREAKING CHANGE:` footer on the commit)
12
+ - [ ] Docs / CI only (no version bump expected)
13
+
14
+ ## Test plan
15
+
16
+ <!-- How a reviewer can verify this. -->
17
+
18
+ - [ ]
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ build-and-test:
16
+ name: build + typecheck + lint + test
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: pnpm/action-setup@v4
22
+
23
+ - uses: actions/setup-node@v4
24
+ with:
25
+ node-version: '{{NODE_VERSION}}'
26
+ cache: pnpm
27
+
28
+ - name: Install dependencies
29
+ run: pnpm install --frozen-lockfile
30
+
31
+ - name: Build
32
+ run: pnpm -r build
33
+
34
+ - name: Typecheck
35
+ run: pnpm -r typecheck
36
+
37
+ - name: Lint
38
+ run: pnpm -r lint
39
+
40
+ - name: Format check
41
+ run: pnpm format:check
42
+
43
+ - name: Test
44
+ run: pnpm -r test
@@ -0,0 +1,62 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ concurrency:
9
+ group: release-${{ github.ref }}
10
+ cancel-in-progress: false
11
+
12
+ jobs:
13
+ release:
14
+ name: semantic-release
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ contents: write
18
+ issues: write
19
+ pull-requests: write
20
+ id-token: write
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ with:
24
+ fetch-depth: 0
25
+ persist-credentials: false
26
+
27
+ - uses: pnpm/action-setup@v4
28
+
29
+ - uses: actions/setup-node@v4
30
+ with:
31
+ node-version: 22
32
+ cache: pnpm
33
+ registry-url: 'https://registry.npmjs.org'
34
+
35
+ - name: Install dependencies
36
+ run: pnpm install --frozen-lockfile
37
+
38
+ - name: Build
39
+ run: pnpm -r build
40
+
41
+ - name: Configure npm auth
42
+ # actions/setup-node writes ~/.npmrc with a ${NODE_AUTH_TOKEN} placeholder
43
+ # that `npm` interpolates at publish time, but `pnpm publish` (used by our
44
+ # semantic-release `publishCmd`) doesn't always expand it. Write the
45
+ # resolved token directly so pnpm reads it unambiguously. Lives in $HOME
46
+ # so it doesn't pollute the repo working tree.
47
+ env:
48
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
49
+ run: |
50
+ if [ -z "${NPM_TOKEN}" ]; then
51
+ echo "::error::NPM_TOKEN secret is not configured"
52
+ exit 1
53
+ fi
54
+ echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "${HOME}/.npmrc"
55
+ echo "registry=https://registry.npmjs.org/" >> "${HOME}/.npmrc"
56
+
57
+ - name: Release
58
+ env:
59
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
61
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
62
+ run: pnpm exec semantic-release