@precisa-saude/cli 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -56,10 +56,27 @@ function readTemplateSource(entry) {
56
56
  return readFileSync(path, "utf-8");
57
57
  }
58
58
  function renderTokens(source, context) {
59
+ return renderTokenSubstitutions(renderConditionalBlocks(source, context), context);
60
+ }
61
+ function renderTokenSubstitutions(source, context) {
59
62
  return source.replace(/\{\{([A-Z_]+)\}\}/g, (match, token) => {
60
63
  return Object.prototype.hasOwnProperty.call(context, token) ? context[token] : match;
61
64
  });
62
65
  }
66
+ function renderConditionalBlocks(source, context) {
67
+ const pattern = /^[ \t]*[^\S\n]*[^\n]*\{\{#if ([A-Z_]+)\}\}[^\n]*\n([\s\S]*?)^[ \t]*[^\n]*\{\{\/if\}\}[^\n]*\n?/gm;
68
+ let prev;
69
+ let out = source;
70
+ do {
71
+ prev = out;
72
+ out = out.replace(pattern, (_match, token, body) => {
73
+ const value = context[token];
74
+ const truthy = value !== void 0 && value !== "" && value !== "false";
75
+ return truthy ? body : "";
76
+ });
77
+ } while (out !== prev);
78
+ return out;
79
+ }
63
80
 
64
81
  // src/manifest.ts
65
82
  import { readFileSync as readFileSync2, writeFileSync } from "fs";
@@ -108,10 +125,16 @@ function tokenContext(manifest) {
108
125
  NODE_VERSION: manifest.nodeVersion ?? "22",
109
126
  OWNER_ORG: manifest.owner,
110
127
  PNPM_VERSION: manifest.pnpmVersion ?? "9.15.9",
128
+ // YAML block-scalar body for the `packages: |` input of `_publish.yml`.
129
+ // The template provides the first entry's indent; subsequent entries
130
+ // get 8 spaces explicitly to match. Empty when no packages declared.
131
+ PUBLISH_PACKAGES_YAML: (manifest.publishPackages ?? []).join("\n "),
111
132
  PUBLISHES_TO_NPM: String(manifest.publishesToNpm),
112
133
  REPO_NAME: manifest.name,
113
134
  REPO_SLUG: `${manifest.owner}/${manifest.name}`,
114
135
  SECURITY_EMAIL: manifest.contactEmails.security,
136
+ SITE_FILTER: manifest.siteFilter ?? "",
137
+ SITE_PROJECT_NAME: manifest.siteProjectName ?? "",
115
138
  VISIBILITY: manifest.visibility
116
139
  };
117
140
  }
@@ -141,6 +164,34 @@ function validateManifest(raw) {
141
164
  if (!Array.isArray(m.commitScopes)) {
142
165
  errors.push({ message: "must be an array of strings", path: "commitScopes" });
143
166
  }
167
+ if (m.publishPackages !== void 0) {
168
+ if (!Array.isArray(m.publishPackages)) {
169
+ errors.push({ message: "must be an array of strings", path: "publishPackages" });
170
+ } else if (m.publishPackages.some((p) => typeof p !== "string" || !p)) {
171
+ errors.push({ message: "entries must be non-empty strings", path: "publishPackages" });
172
+ }
173
+ }
174
+ if (m.publishesToNpm) {
175
+ if (!Array.isArray(m.publishPackages) || m.publishPackages.length === 0) {
176
+ errors.push({
177
+ message: "must have at least one entry when publishesToNpm is true",
178
+ path: "publishPackages"
179
+ });
180
+ }
181
+ }
182
+ if (m.hasSite === true) {
183
+ for (const key of ["siteProjectName", "siteFilter"]) {
184
+ if (typeof m[key] !== "string" || !m[key]) {
185
+ errors.push({ message: "required and non-empty when hasSite is true", path: key });
186
+ }
187
+ }
188
+ }
189
+ if (m.siteProjectName !== void 0 && typeof m.siteProjectName !== "string") {
190
+ errors.push({ message: "must be a string", path: "siteProjectName" });
191
+ }
192
+ if (m.siteFilter !== void 0 && typeof m.siteFilter !== "string") {
193
+ errors.push({ message: "must be a string", path: "siteFilter" });
194
+ }
144
195
  if (!m.contactEmails || typeof m.contactEmails !== "object") {
145
196
  errors.push({ message: "must be an object", path: "contactEmails" });
146
197
  } else {
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bin.ts","../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":["#!/usr/bin/env node\nimport { Command } from 'commander';\n\nimport { runDoctor } from './commands/doctor.js';\nimport { runNew } from './commands/new.js';\nimport { runSync } from './commands/sync.js';\n\nconst program = new Command();\n\nprogram\n .name('precisa')\n .description('Bootstrap and sync Precisa Saúde repositories.')\n .version('0.0.0');\n\nprogram\n .command('new <repo-name>')\n .description('Scaffold a new repository from the shared templates.')\n .option('--profile <profile>', 'preset: oss-library | oss-site | private-app', 'oss-library')\n .option('--dry-run', 'print what would be written without touching disk', false)\n .option('--non-interactive', 'skip prompts; use --owner/--scopes/etc. flags instead', false)\n .option('--owner <org>', 'GitHub owner (org or user)', 'Precisa-Saude')\n .option(\n '--scopes <csv>',\n 'Comma-separated commit scopes (e.g. \"core,docs,ci,deps\")',\n 'docs,ci,deps',\n )\n .option('--security-email <email>', 'Security contact email', 'security@precisa-saude.com.br')\n .option(\n '--conduct-email <email>',\n 'Code-of-conduct contact email',\n 'conduct@precisa-saude.com.br',\n )\n .action(\n async (\n repoName: string,\n opts: {\n profile: string;\n dryRun: boolean;\n nonInteractive: boolean;\n owner: string;\n scopes: string;\n securityEmail: string;\n conductEmail: string;\n },\n ) => {\n await runNew(repoName, opts);\n },\n );\n\nprogram\n .command('sync')\n .description(\"Re-render templates against this repo's .precisa.json manifest.\")\n .option('--dry-run', 'print a diff without writing', false)\n .action(async (opts: { dryRun: boolean }) => {\n await runSync(opts);\n });\n\nprogram\n .command('doctor')\n .description('Audit this repo against the templates and report drift.')\n .action(async () => {\n await runDoctor();\n });\n\nprogram.parseAsync(process.argv).catch((err: unknown) => {\n console.error(err);\n process.exit(1);\n});\n","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 (current === rendered) {\n return { message: '', severity: 'ok', target: entry.target };\n }\n\n // Non-overwrite strategies don't re-render on sync — treat drift as\n // informational, not a warning. Makes the doctor output scannable:\n // warnings are the files sync WOULD change; infos are files where\n // drift is intentional (customized scaffold, preserved docs).\n if (entry.merge_strategy === 'preserve') {\n return {\n message: 'Differs from template (preserve strategy — suggestion only)',\n severity: 'info',\n target: entry.target,\n };\n }\n if (entry.merge_strategy === 'skip_if_exists') {\n return {\n message: 'Differs from template (scaffold-only; sync will not overwrite)',\n severity: 'info',\n target: entry.target,\n };\n }\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 | 'never'\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 'never':\n return false;\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 // For human-readable docs (AGENTS.md, README) — backticked list.\n COMMIT_SCOPES_HUMAN: manifest.commitScopes.map((s) => `\\`${s}\\``).join(', '),\n // For `.commitlintrc.cjs` — scope array spelled as JS string literals,\n // ready to drop inside `[ ... ]`.\n COMMIT_SCOPES_JSON: manifest.commitScopes.map((s) => `'${s}'`).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 conductEmail?: string;\n dryRun: boolean;\n /** Skip interactive prompts; require --owner/--scopes/--*-email flags. */\n nonInteractive?: boolean;\n owner?: string;\n profile: string;\n /** Comma-separated list, e.g. \"core,docs,ci,deps\". */\n scopes?: string;\n securityEmail?: 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 = opts.nonInteractive\n ? {\n commitScopes: (opts.scopes ?? 'docs,ci,deps')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean),\n conductEmail: opts.conductEmail ?? 'conduct@precisa-saude.com.br',\n owner: opts.owner ?? 'Precisa-Saude',\n securityEmail: opts.securityEmail ?? 'security@precisa-saude.com.br',\n }\n : await prompts(\n [\n {\n initial: opts.owner ?? 'Precisa-Saude',\n message: 'GitHub owner (org or user):',\n name: 'owner',\n type: 'text',\n },\n {\n initial: opts.securityEmail ?? 'security@precisa-saude.com.br',\n message: 'Security contact email:',\n name: 'securityEmail',\n type: 'text',\n },\n {\n initial: opts.conductEmail ?? 'conduct@precisa-saude.com.br',\n message: 'Code-of-conduct contact email:',\n name: 'conductEmail',\n type: 'text',\n },\n {\n initial: opts.scopes ?? '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"]}
1
+ {"version":3,"sources":["../src/bin.ts","../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":["#!/usr/bin/env node\nimport { Command } from 'commander';\n\nimport { runDoctor } from './commands/doctor.js';\nimport { runNew } from './commands/new.js';\nimport { runSync } from './commands/sync.js';\n\nconst program = new Command();\n\nprogram\n .name('precisa')\n .description('Bootstrap and sync Precisa Saúde repositories.')\n .version('0.0.0');\n\nprogram\n .command('new <repo-name>')\n .description('Scaffold a new repository from the shared templates.')\n .option('--profile <profile>', 'preset: oss-library | oss-site | private-app', 'oss-library')\n .option('--dry-run', 'print what would be written without touching disk', false)\n .option('--non-interactive', 'skip prompts; use --owner/--scopes/etc. flags instead', false)\n .option('--owner <org>', 'GitHub owner (org or user)', 'Precisa-Saude')\n .option(\n '--scopes <csv>',\n 'Comma-separated commit scopes (e.g. \"core,docs,ci,deps\")',\n 'docs,ci,deps',\n )\n .option('--security-email <email>', 'Security contact email', 'security@precisa-saude.com.br')\n .option(\n '--conduct-email <email>',\n 'Code-of-conduct contact email',\n 'conduct@precisa-saude.com.br',\n )\n .action(\n async (\n repoName: string,\n opts: {\n profile: string;\n dryRun: boolean;\n nonInteractive: boolean;\n owner: string;\n scopes: string;\n securityEmail: string;\n conductEmail: string;\n },\n ) => {\n await runNew(repoName, opts);\n },\n );\n\nprogram\n .command('sync')\n .description(\"Re-render templates against this repo's .precisa.json manifest.\")\n .option('--dry-run', 'print a diff without writing', false)\n .action(async (opts: { dryRun: boolean }) => {\n await runSync(opts);\n });\n\nprogram\n .command('doctor')\n .description('Audit this repo against the templates and report drift.')\n .action(async () => {\n await runDoctor();\n });\n\nprogram.parseAsync(process.argv).catch((err: unknown) => {\n console.error(err);\n process.exit(1);\n});\n","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 (current === rendered) {\n return { message: '', severity: 'ok', target: entry.target };\n }\n\n // Non-overwrite strategies don't re-render on sync — treat drift as\n // informational, not a warning. Makes the doctor output scannable:\n // warnings are the files sync WOULD change; infos are files where\n // drift is intentional (customized scaffold, preserved docs).\n if (entry.merge_strategy === 'preserve') {\n return {\n message: 'Differs from template (preserve strategy — suggestion only)',\n severity: 'info',\n target: entry.target,\n };\n }\n if (entry.merge_strategy === 'skip_if_exists') {\n return {\n message: 'Differs from template (scaffold-only; sync will not overwrite)',\n severity: 'info',\n target: entry.target,\n };\n }\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 *\n * Also processes conditional blocks:\n *\n * {{#if HAS_SITE}}\n * ...lines rendered only when HAS_SITE is truthy...\n * {{/if}}\n *\n * Markers can appear inside a host-language comment so the template stays\n * parseable by syntax-aware tools (prettier on YAML, for example):\n *\n * # {{#if HAS_SITE}}\n * ...\n * # {{/if}}\n *\n * A token is \"truthy\" when its string value is non-empty and not `\"false\"`.\n * Blocks may nest; they are expanded before token substitution so a block's\n * body can itself reference tokens.\n */\nexport function renderTokens(source: string, context: Record<string, string>): string {\n return renderTokenSubstitutions(renderConditionalBlocks(source, context), context);\n}\n\nfunction renderTokenSubstitutions(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\nfunction renderConditionalBlocks(source: string, context: Record<string, string>): string {\n // Match an entire line containing `{{#if TOKEN}}` (possibly wrapped in a\n // host-language comment like `# {{#if TOKEN}}` or `// {{#if TOKEN}}`),\n // up through the matching `{{/if}}` line. The whole marker line is\n // consumed so commented-out markers don't leak into rendered output.\n const pattern =\n /^[ \\t]*[^\\S\\n]*[^\\n]*\\{\\{#if ([A-Z_]+)\\}\\}[^\\n]*\\n([\\s\\S]*?)^[ \\t]*[^\\n]*\\{\\{\\/if\\}\\}[^\\n]*\\n?/gm;\n let prev: string;\n let out = source;\n // Re-run to handle nested blocks. Simple fixed-point loop keeps the\n // replacement rules uniform.\n do {\n prev = out;\n out = out.replace(pattern, (_match, token: string, body: string) => {\n const value = context[token];\n const truthy = value !== undefined && value !== '' && value !== 'false';\n return truthy ? body : '';\n });\n } while (out !== prev);\n return out;\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 /**\n * Workspace package directories (relative to repo root) that the\n * `publish` job in `ci.yml` should pass to `_publish.yml`. Only read\n * when `publishesToNpm: true`. Example: `[\"packages/core\", \"packages/cli\"]`.\n */\n publishPackages?: string[];\n\n /** Schema version of this manifest file. Bump when the schema changes. */\n schemaVersion: 1;\n\n /**\n * pnpm filter selector for the site package, passed to the deploy\n * workflow. Only read when `hasSite: true`. Example: `@my-repo/site`.\n */\n siteFilter?: string;\n\n /**\n * Cloudflare Pages project name for the deploy-site workflow. Only\n * read when `hasSite: true`.\n */\n siteProjectName?: string;\n\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 | 'never'\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 'never':\n return false;\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 // For human-readable docs (AGENTS.md, README) — backticked list.\n COMMIT_SCOPES_HUMAN: manifest.commitScopes.map((s) => `\\`${s}\\``).join(', '),\n // For `.commitlintrc.cjs` — scope array spelled as JS string literals,\n // ready to drop inside `[ ... ]`.\n COMMIT_SCOPES_JSON: manifest.commitScopes.map((s) => `'${s}'`).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 // YAML block-scalar body for the `packages: |` input of `_publish.yml`.\n // The template provides the first entry's indent; subsequent entries\n // get 8 spaces explicitly to match. Empty when no packages declared.\n PUBLISH_PACKAGES_YAML: (manifest.publishPackages ?? []).join('\\n '),\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 SITE_FILTER: manifest.siteFilter ?? '',\n SITE_PROJECT_NAME: manifest.siteProjectName ?? '',\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.publishPackages !== undefined) {\n if (!Array.isArray(m.publishPackages)) {\n errors.push({ message: 'must be an array of strings', path: 'publishPackages' });\n } else if (m.publishPackages.some((p) => typeof p !== 'string' || !p)) {\n errors.push({ message: 'entries must be non-empty strings', path: 'publishPackages' });\n }\n }\n if (m.publishesToNpm) {\n if (!Array.isArray(m.publishPackages) || m.publishPackages.length === 0) {\n errors.push({\n message: 'must have at least one entry when publishesToNpm is true',\n path: 'publishPackages',\n });\n }\n }\n if (m.hasSite === true) {\n for (const key of ['siteProjectName', 'siteFilter'] as const) {\n if (typeof m[key] !== 'string' || !m[key]) {\n errors.push({ message: 'required and non-empty when hasSite is true', path: key });\n }\n }\n }\n if (m.siteProjectName !== undefined && typeof m.siteProjectName !== 'string') {\n errors.push({ message: 'must be a string', path: 'siteProjectName' });\n }\n if (m.siteFilter !== undefined && typeof m.siteFilter !== 'string') {\n errors.push({ message: 'must be a string', path: 'siteFilter' });\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 conductEmail?: string;\n dryRun: boolean;\n /** Skip interactive prompts; require --owner/--scopes/--*-email flags. */\n nonInteractive?: boolean;\n owner?: string;\n profile: string;\n /** Comma-separated list, e.g. \"core,docs,ci,deps\". */\n scopes?: string;\n securityEmail?: 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 = opts.nonInteractive\n ? {\n commitScopes: (opts.scopes ?? 'docs,ci,deps')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean),\n conductEmail: opts.conductEmail ?? 'conduct@precisa-saude.com.br',\n owner: opts.owner ?? 'Precisa-Saude',\n securityEmail: opts.securityEmail ?? 'security@precisa-saude.com.br',\n }\n : await prompts(\n [\n {\n initial: opts.owner ?? 'Precisa-Saude',\n message: 'GitHub owner (org or user):',\n name: 'owner',\n type: 'text',\n },\n {\n initial: opts.securityEmail ?? 'security@precisa-saude.com.br',\n message: 'Security contact email:',\n name: 'securityEmail',\n type: 'text',\n },\n {\n initial: opts.conductEmail ?? 'conduct@precisa-saude.com.br',\n message: 'Code-of-conduct contact email:',\n name: 'conductEmail',\n type: 'text',\n },\n {\n initial: opts.scopes ?? '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"]}
package/dist/index.d.ts CHANGED
@@ -44,8 +44,24 @@ interface PrecisaManifest {
44
44
  pnpmVersion?: string;
45
45
  /** Publishes any workspace package to npm? */
46
46
  publishesToNpm: boolean;
47
+ /**
48
+ * Workspace package directories (relative to repo root) that the
49
+ * `publish` job in `ci.yml` should pass to `_publish.yml`. Only read
50
+ * when `publishesToNpm: true`. Example: `["packages/core", "packages/cli"]`.
51
+ */
52
+ publishPackages?: string[];
47
53
  /** Schema version of this manifest file. Bump when the schema changes. */
48
54
  schemaVersion: 1;
55
+ /**
56
+ * pnpm filter selector for the site package, passed to the deploy
57
+ * workflow. Only read when `hasSite: true`. Example: `@my-repo/site`.
58
+ */
59
+ siteFilter?: string;
60
+ /**
61
+ * Cloudflare Pages project name for the deploy-site workflow. Only
62
+ * read when `hasSite: true`.
63
+ */
64
+ siteProjectName?: string;
49
65
  /** Public-OSS or private-internal. Controls which templates are rendered. */
50
66
  visibility: 'oss' | 'private';
51
67
  }
package/dist/index.js CHANGED
@@ -51,10 +51,27 @@ function readTemplateSource(entry) {
51
51
  return readFileSync(path, "utf-8");
52
52
  }
53
53
  function renderTokens(source, context) {
54
+ return renderTokenSubstitutions(renderConditionalBlocks(source, context), context);
55
+ }
56
+ function renderTokenSubstitutions(source, context) {
54
57
  return source.replace(/\{\{([A-Z_]+)\}\}/g, (match, token) => {
55
58
  return Object.prototype.hasOwnProperty.call(context, token) ? context[token] : match;
56
59
  });
57
60
  }
61
+ function renderConditionalBlocks(source, context) {
62
+ const pattern = /^[ \t]*[^\S\n]*[^\n]*\{\{#if ([A-Z_]+)\}\}[^\n]*\n([\s\S]*?)^[ \t]*[^\n]*\{\{\/if\}\}[^\n]*\n?/gm;
63
+ let prev;
64
+ let out = source;
65
+ do {
66
+ prev = out;
67
+ out = out.replace(pattern, (_match, token, body) => {
68
+ const value = context[token];
69
+ const truthy = value !== void 0 && value !== "" && value !== "false";
70
+ return truthy ? body : "";
71
+ });
72
+ } while (out !== prev);
73
+ return out;
74
+ }
58
75
 
59
76
  // src/manifest.ts
60
77
  import { readFileSync as readFileSync2, writeFileSync } from "fs";
@@ -103,10 +120,16 @@ function tokenContext(manifest) {
103
120
  NODE_VERSION: manifest.nodeVersion ?? "22",
104
121
  OWNER_ORG: manifest.owner,
105
122
  PNPM_VERSION: manifest.pnpmVersion ?? "9.15.9",
123
+ // YAML block-scalar body for the `packages: |` input of `_publish.yml`.
124
+ // The template provides the first entry's indent; subsequent entries
125
+ // get 8 spaces explicitly to match. Empty when no packages declared.
126
+ PUBLISH_PACKAGES_YAML: (manifest.publishPackages ?? []).join("\n "),
106
127
  PUBLISHES_TO_NPM: String(manifest.publishesToNpm),
107
128
  REPO_NAME: manifest.name,
108
129
  REPO_SLUG: `${manifest.owner}/${manifest.name}`,
109
130
  SECURITY_EMAIL: manifest.contactEmails.security,
131
+ SITE_FILTER: manifest.siteFilter ?? "",
132
+ SITE_PROJECT_NAME: manifest.siteProjectName ?? "",
110
133
  VISIBILITY: manifest.visibility
111
134
  };
112
135
  }
@@ -136,6 +159,34 @@ function validateManifest(raw) {
136
159
  if (!Array.isArray(m.commitScopes)) {
137
160
  errors.push({ message: "must be an array of strings", path: "commitScopes" });
138
161
  }
162
+ if (m.publishPackages !== void 0) {
163
+ if (!Array.isArray(m.publishPackages)) {
164
+ errors.push({ message: "must be an array of strings", path: "publishPackages" });
165
+ } else if (m.publishPackages.some((p) => typeof p !== "string" || !p)) {
166
+ errors.push({ message: "entries must be non-empty strings", path: "publishPackages" });
167
+ }
168
+ }
169
+ if (m.publishesToNpm) {
170
+ if (!Array.isArray(m.publishPackages) || m.publishPackages.length === 0) {
171
+ errors.push({
172
+ message: "must have at least one entry when publishesToNpm is true",
173
+ path: "publishPackages"
174
+ });
175
+ }
176
+ }
177
+ if (m.hasSite === true) {
178
+ for (const key of ["siteProjectName", "siteFilter"]) {
179
+ if (typeof m[key] !== "string" || !m[key]) {
180
+ errors.push({ message: "required and non-empty when hasSite is true", path: key });
181
+ }
182
+ }
183
+ }
184
+ if (m.siteProjectName !== void 0 && typeof m.siteProjectName !== "string") {
185
+ errors.push({ message: "must be a string", path: "siteProjectName" });
186
+ }
187
+ if (m.siteFilter !== void 0 && typeof m.siteFilter !== "string") {
188
+ errors.push({ message: "must be a string", path: "siteFilter" });
189
+ }
139
190
  if (!m.contactEmails || typeof m.contactEmails !== "object") {
140
191
  errors.push({ message: "must be an object", path: "contactEmails" });
141
192
  } else {
package/dist/index.js.map CHANGED
@@ -1 +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 (current === rendered) {\n return { message: '', severity: 'ok', target: entry.target };\n }\n\n // Non-overwrite strategies don't re-render on sync — treat drift as\n // informational, not a warning. Makes the doctor output scannable:\n // warnings are the files sync WOULD change; infos are files where\n // drift is intentional (customized scaffold, preserved docs).\n if (entry.merge_strategy === 'preserve') {\n return {\n message: 'Differs from template (preserve strategy — suggestion only)',\n severity: 'info',\n target: entry.target,\n };\n }\n if (entry.merge_strategy === 'skip_if_exists') {\n return {\n message: 'Differs from template (scaffold-only; sync will not overwrite)',\n severity: 'info',\n target: entry.target,\n };\n }\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 | 'never'\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 'never':\n return false;\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 // For human-readable docs (AGENTS.md, README) — backticked list.\n COMMIT_SCOPES_HUMAN: manifest.commitScopes.map((s) => `\\`${s}\\``).join(', '),\n // For `.commitlintrc.cjs` — scope array spelled as JS string literals,\n // ready to drop inside `[ ... ]`.\n COMMIT_SCOPES_JSON: manifest.commitScopes.map((s) => `'${s}'`).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 conductEmail?: string;\n dryRun: boolean;\n /** Skip interactive prompts; require --owner/--scopes/--*-email flags. */\n nonInteractive?: boolean;\n owner?: string;\n profile: string;\n /** Comma-separated list, e.g. \"core,docs,ci,deps\". */\n scopes?: string;\n securityEmail?: 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 = opts.nonInteractive\n ? {\n commitScopes: (opts.scopes ?? 'docs,ci,deps')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean),\n conductEmail: opts.conductEmail ?? 'conduct@precisa-saude.com.br',\n owner: opts.owner ?? 'Precisa-Saude',\n securityEmail: opts.securityEmail ?? 'security@precisa-saude.com.br',\n }\n : await prompts(\n [\n {\n initial: opts.owner ?? 'Precisa-Saude',\n message: 'GitHub owner (org or user):',\n name: 'owner',\n type: 'text',\n },\n {\n initial: opts.securityEmail ?? 'security@precisa-saude.com.br',\n message: 'Security contact email:',\n name: 'securityEmail',\n type: 'text',\n },\n {\n initial: opts.conductEmail ?? 'conduct@precisa-saude.com.br',\n message: 'Code-of-conduct contact email:',\n name: 'conductEmail',\n type: 'text',\n },\n {\n initial: opts.scopes ?? '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"]}
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 (current === rendered) {\n return { message: '', severity: 'ok', target: entry.target };\n }\n\n // Non-overwrite strategies don't re-render on sync — treat drift as\n // informational, not a warning. Makes the doctor output scannable:\n // warnings are the files sync WOULD change; infos are files where\n // drift is intentional (customized scaffold, preserved docs).\n if (entry.merge_strategy === 'preserve') {\n return {\n message: 'Differs from template (preserve strategy — suggestion only)',\n severity: 'info',\n target: entry.target,\n };\n }\n if (entry.merge_strategy === 'skip_if_exists') {\n return {\n message: 'Differs from template (scaffold-only; sync will not overwrite)',\n severity: 'info',\n target: entry.target,\n };\n }\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 *\n * Also processes conditional blocks:\n *\n * {{#if HAS_SITE}}\n * ...lines rendered only when HAS_SITE is truthy...\n * {{/if}}\n *\n * Markers can appear inside a host-language comment so the template stays\n * parseable by syntax-aware tools (prettier on YAML, for example):\n *\n * # {{#if HAS_SITE}}\n * ...\n * # {{/if}}\n *\n * A token is \"truthy\" when its string value is non-empty and not `\"false\"`.\n * Blocks may nest; they are expanded before token substitution so a block's\n * body can itself reference tokens.\n */\nexport function renderTokens(source: string, context: Record<string, string>): string {\n return renderTokenSubstitutions(renderConditionalBlocks(source, context), context);\n}\n\nfunction renderTokenSubstitutions(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\nfunction renderConditionalBlocks(source: string, context: Record<string, string>): string {\n // Match an entire line containing `{{#if TOKEN}}` (possibly wrapped in a\n // host-language comment like `# {{#if TOKEN}}` or `// {{#if TOKEN}}`),\n // up through the matching `{{/if}}` line. The whole marker line is\n // consumed so commented-out markers don't leak into rendered output.\n const pattern =\n /^[ \\t]*[^\\S\\n]*[^\\n]*\\{\\{#if ([A-Z_]+)\\}\\}[^\\n]*\\n([\\s\\S]*?)^[ \\t]*[^\\n]*\\{\\{\\/if\\}\\}[^\\n]*\\n?/gm;\n let prev: string;\n let out = source;\n // Re-run to handle nested blocks. Simple fixed-point loop keeps the\n // replacement rules uniform.\n do {\n prev = out;\n out = out.replace(pattern, (_match, token: string, body: string) => {\n const value = context[token];\n const truthy = value !== undefined && value !== '' && value !== 'false';\n return truthy ? body : '';\n });\n } while (out !== prev);\n return out;\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 /**\n * Workspace package directories (relative to repo root) that the\n * `publish` job in `ci.yml` should pass to `_publish.yml`. Only read\n * when `publishesToNpm: true`. Example: `[\"packages/core\", \"packages/cli\"]`.\n */\n publishPackages?: string[];\n\n /** Schema version of this manifest file. Bump when the schema changes. */\n schemaVersion: 1;\n\n /**\n * pnpm filter selector for the site package, passed to the deploy\n * workflow. Only read when `hasSite: true`. Example: `@my-repo/site`.\n */\n siteFilter?: string;\n\n /**\n * Cloudflare Pages project name for the deploy-site workflow. Only\n * read when `hasSite: true`.\n */\n siteProjectName?: string;\n\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 | 'never'\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 'never':\n return false;\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 // For human-readable docs (AGENTS.md, README) — backticked list.\n COMMIT_SCOPES_HUMAN: manifest.commitScopes.map((s) => `\\`${s}\\``).join(', '),\n // For `.commitlintrc.cjs` — scope array spelled as JS string literals,\n // ready to drop inside `[ ... ]`.\n COMMIT_SCOPES_JSON: manifest.commitScopes.map((s) => `'${s}'`).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 // YAML block-scalar body for the `packages: |` input of `_publish.yml`.\n // The template provides the first entry's indent; subsequent entries\n // get 8 spaces explicitly to match. Empty when no packages declared.\n PUBLISH_PACKAGES_YAML: (manifest.publishPackages ?? []).join('\\n '),\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 SITE_FILTER: manifest.siteFilter ?? '',\n SITE_PROJECT_NAME: manifest.siteProjectName ?? '',\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.publishPackages !== undefined) {\n if (!Array.isArray(m.publishPackages)) {\n errors.push({ message: 'must be an array of strings', path: 'publishPackages' });\n } else if (m.publishPackages.some((p) => typeof p !== 'string' || !p)) {\n errors.push({ message: 'entries must be non-empty strings', path: 'publishPackages' });\n }\n }\n if (m.publishesToNpm) {\n if (!Array.isArray(m.publishPackages) || m.publishPackages.length === 0) {\n errors.push({\n message: 'must have at least one entry when publishesToNpm is true',\n path: 'publishPackages',\n });\n }\n }\n if (m.hasSite === true) {\n for (const key of ['siteProjectName', 'siteFilter'] as const) {\n if (typeof m[key] !== 'string' || !m[key]) {\n errors.push({ message: 'required and non-empty when hasSite is true', path: key });\n }\n }\n }\n if (m.siteProjectName !== undefined && typeof m.siteProjectName !== 'string') {\n errors.push({ message: 'must be a string', path: 'siteProjectName' });\n }\n if (m.siteFilter !== undefined && typeof m.siteFilter !== 'string') {\n errors.push({ message: 'must be a string', path: 'siteFilter' });\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 conductEmail?: string;\n dryRun: boolean;\n /** Skip interactive prompts; require --owner/--scopes/--*-email flags. */\n nonInteractive?: boolean;\n owner?: string;\n profile: string;\n /** Comma-separated list, e.g. \"core,docs,ci,deps\". */\n scopes?: string;\n securityEmail?: 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 = opts.nonInteractive\n ? {\n commitScopes: (opts.scopes ?? 'docs,ci,deps')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean),\n conductEmail: opts.conductEmail ?? 'conduct@precisa-saude.com.br',\n owner: opts.owner ?? 'Precisa-Saude',\n securityEmail: opts.securityEmail ?? 'security@precisa-saude.com.br',\n }\n : await prompts(\n [\n {\n initial: opts.owner ?? 'Precisa-Saude',\n message: 'GitHub owner (org or user):',\n name: 'owner',\n type: 'text',\n },\n {\n initial: opts.securityEmail ?? 'security@precisa-saude.com.br',\n message: 'Security contact email:',\n name: 'securityEmail',\n type: 'text',\n },\n {\n initial: opts.conductEmail ?? 'conduct@precisa-saude.com.br',\n message: 'Code-of-conduct contact email:',\n name: 'conductEmail',\n type: 'text',\n },\n {\n initial: opts.scopes ?? '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"]}
@@ -1 +1,3 @@
1
- shamefully-hoist=false
1
+ # Intentionally empty. pnpm defaults are fine for the ecosystem;
2
+ # avoid `shamefully-hoist=false` (the default anyway) because npm warns
3
+ # on unknown keys and the noise shows up in every `npm view`/`npm deprecate`.
@@ -10,11 +10,9 @@ name: CI/CD
10
10
  # review.yml — automated Anthropic → OpenAI code review
11
11
  # publish-tag.yml — manual recovery (workflow_dispatch)
12
12
  #
13
- # This orchestrator wires the pieces together. Each repo adapts:
14
- # - Which reusable workflows to call (comment out what doesn't apply)
15
- # - The publish `packages` list
16
- # - The site `project_name` and `site_filter`
17
- # - Extra repo-specific workflows (e.g. fhir's publish-ig.yml)
13
+ # Per-repo customization flows through `.precisa.json`:
14
+ # publishesToNpm + publishPackages[] gate the `publish` job
15
+ # hasSite + siteProjectName + siteFilter gate the `deploy-site` job
18
16
 
19
17
  on:
20
18
  push:
@@ -30,7 +28,6 @@ permissions:
30
28
  contents: write
31
29
  issues: write
32
30
  pull-requests: write
33
- deployments: write
34
31
  id-token: write
35
32
  # `models: read` pelo mesmo motivo: review.yml chama GitHub Models
36
33
  # via GITHUB_TOKEN no fallback de providers.
@@ -54,6 +51,7 @@ jobs:
54
51
  with:
55
52
  pr_number: ${{ github.event.pull_request.number }}
56
53
  secrets: inherit
54
+ # {{#if PUBLISHES_TO_NPM}}
57
55
 
58
56
  # ── Release (main only, guarded on package changes) ────────────
59
57
  release:
@@ -71,16 +69,18 @@ jobs:
71
69
  release_sha: ${{ needs.release.outputs.release_sha }}
72
70
  # Newline-separated list of package directories. One per line.
73
71
  packages: |
74
- packages/core
75
- packages/calculators
72
+ {{PUBLISH_PACKAGES_YAML}}
76
73
  secrets: inherit
74
+ # {{/if}}
75
+ # {{#if HAS_SITE}}
77
76
 
78
- # ── Deploy site (repos with a site — remove if not applicable) ──
77
+ # ── Deploy site (Cloudflare Pages + Slack notify) ──────────────
79
78
  deploy-site:
80
79
  needs: checks
81
80
  if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
82
81
  uses: ./.github/workflows/_deploy-site.yml
83
82
  with:
84
- project_name: '{{PROJECT_NAME}}'
85
- site_filter: '@{{SITE_FILTER}}'
83
+ project_name: '{{SITE_PROJECT_NAME}}'
84
+ site_filter: '{{SITE_FILTER}}'
86
85
  secrets: inherit
86
+ # {{/if}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@precisa-saude/cli",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "description": "Bootstrap and sync Precisa Saúde repositories — the `precisa` CLI.",
5
5
  "keywords": [
6
6
  "cli",
@@ -45,7 +45,7 @@
45
45
  "@types/prompts": "^2.4.9",
46
46
  "tsup": "^8.3.5",
47
47
  "typescript": "~5.7.3",
48
- "@precisa-saude/tsconfig": "1.6.1"
48
+ "@precisa-saude/tsconfig": "1.7.0"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=22.0.0"