@contentrain/mcp 2.3.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{annotations-D3tlsF38.mjs → annotations-C-tQVmot.mjs} +15 -1
- package/dist/annotations-C-tQVmot.mjs.map +1 -0
- package/dist/{apply-manager-SLCRLHN_.mjs → apply-manager-BybvnUVC.mjs} +28 -18
- package/dist/apply-manager-BybvnUVC.mjs.map +1 -0
- package/dist/{astro-parser-LNjEspnf.mjs → astro-parser-Cb9uuBpW.mjs} +3 -3
- package/dist/{astro-parser-LNjEspnf.mjs.map → astro-parser-Cb9uuBpW.mjs.map} +1 -1
- package/dist/{availability-sWWRz2W0.mjs → availability-Dgcmvv99.mjs} +1 -1
- package/dist/{availability-sWWRz2W0.mjs.map → availability-Dgcmvv99.mjs.map} +1 -1
- package/dist/{branch-lifecycle-BAfgSQBv.mjs → branch-lifecycle-B6PXZ2N7.mjs} +11 -61
- package/dist/branch-lifecycle-B6PXZ2N7.mjs.map +1 -0
- package/dist/{branch-lifecycle-D4W0WIs4.d.mts → branch-lifecycle-DhzHfzs3.d.mts} +1 -1
- package/dist/{branch-lifecycle-D4W0WIs4.d.mts.map → branch-lifecycle-DhzHfzs3.d.mts.map} +1 -1
- package/dist/{compiler-CNzm2Y2I.mjs → compiler-B6hNQeCC.mjs} +2 -2
- package/dist/{compiler-CNzm2Y2I.mjs.map → compiler-B6hNQeCC.mjs.map} +1 -1
- package/dist/{compiler-sfc.cjs-y0B0L3MH.mjs → compiler-sfc.cjs-8yY7b5bx.mjs} +2 -2
- package/dist/{compiler-sfc.cjs-y0B0L3MH.mjs.map → compiler-sfc.cjs-8yY7b5bx.mjs.map} +1 -1
- package/dist/{config-oxxgznz7.mjs → config-VlRKRwAS.mjs} +1 -1
- package/dist/{config-oxxgznz7.mjs.map → config-VlRKRwAS.mjs.map} +1 -1
- package/dist/{content-manager-DWo3G64y.d.mts → content-manager-Dn4tqDw1.d.mts} +10 -7
- package/dist/content-manager-Dn4tqDw1.d.mts.map +1 -0
- package/dist/{context-DjglDPvj.mjs → context-C3x6I3cE.mjs} +4 -4
- package/dist/{context-DjglDPvj.mjs.map → context-C3x6I3cE.mjs.map} +1 -1
- package/dist/core/apply-manager.d.mts +8 -0
- package/dist/core/apply-manager.d.mts.map +1 -1
- package/dist/core/apply-manager.mjs +10 -9
- package/dist/core/config.d.mts +1 -1
- package/dist/core/config.mjs +1 -1
- package/dist/core/content-manager.d.mts +2 -2
- package/dist/core/content-manager.mjs +2 -2
- package/dist/core/context.d.mts +1 -1
- package/dist/core/context.mjs +5 -5
- package/dist/core/contracts/index.d.mts +2 -2
- package/dist/core/doctor.mjs +7 -6
- package/dist/core/graph-builder.mjs +2 -2
- package/dist/core/meta-manager.d.mts +8 -9
- package/dist/core/meta-manager.d.mts.map +1 -1
- package/dist/core/meta-manager.mjs +2 -2
- package/dist/core/model-manager.d.mts +39 -3
- package/dist/core/model-manager.d.mts.map +1 -1
- package/dist/core/model-manager.mjs +3 -3
- package/dist/core/ops/index.d.mts +40 -3
- package/dist/core/ops/index.d.mts.map +1 -1
- package/dist/core/ops/index.mjs +4 -4
- package/dist/core/overlay-reader.d.mts +1 -1
- package/dist/core/overlay-reader.mjs +1 -1
- package/dist/core/scan-config.mjs +1 -1
- package/dist/core/scanner.mjs +3 -3
- package/dist/core/validator/index.d.mts +1 -1
- package/dist/core/validator/index.d.mts.map +1 -1
- package/dist/core/validator/index.mjs +4 -4
- package/dist/{detect-CdDU4Ew_.mjs → detect-B0oXbQaP.mjs} +1 -1
- package/dist/{detect-CdDU4Ew_.mjs.map → detect-B0oXbQaP.mjs.map} +1 -1
- package/dist/{doctor-BwJ_nmqS.mjs → doctor-DNPuGgcg.mjs} +28 -15
- package/dist/doctor-DNPuGgcg.mjs.map +1 -0
- package/dist/{errors-e0YdjooK.mjs → errors-CvBS12e7.mjs} +1 -1
- package/dist/{errors-e0YdjooK.mjs.map → errors-CvBS12e7.mjs.map} +1 -1
- package/dist/fs-DLbVB-Ek.mjs.map +1 -1
- package/dist/git/branch-lifecycle.d.mts +1 -1
- package/dist/git/branch-lifecycle.mjs +3 -2
- package/dist/git/identity.d.mts +42 -0
- package/dist/git/identity.d.mts.map +1 -0
- package/dist/git/identity.mjs +2 -0
- package/dist/git/transaction.d.mts +1 -1
- package/dist/git/transaction.d.mts.map +1 -1
- package/dist/git/transaction.mjs +9 -8
- package/dist/{graph-builder-DK4Mh8Tn.mjs → graph-builder-BEocwW13.mjs} +2 -2
- package/dist/{graph-builder-DK4Mh8Tn.mjs.map → graph-builder-BEocwW13.mjs.map} +1 -1
- package/dist/{id-DV_T9Ic8.mjs → id-BjhaIxTL.mjs} +1 -1
- package/dist/{id-DV_T9Ic8.mjs.map → id-BjhaIxTL.mjs.map} +1 -1
- package/dist/identity-D8QoGxhT.mjs +125 -0
- package/dist/identity-D8QoGxhT.mjs.map +1 -0
- package/dist/{index-w8QHThNS.d.mts → index-BWbTJlXm.d.mts} +2 -2
- package/dist/index.mjs +21 -20
- package/dist/index.mjs.map +1 -1
- package/dist/{local-8dJ5NIWL.mjs → local-Db5X9mjZ.mjs} +25 -13
- package/dist/local-Db5X9mjZ.mjs.map +1 -0
- package/dist/{meta-manager-CJUiTgP2.mjs → meta-manager-B_Se183X.mjs} +29 -5
- package/dist/meta-manager-B_Se183X.mjs.map +1 -0
- package/dist/{model-manager-DP2CZiMT.mjs → model-manager-DgyKMjLN.mjs} +169 -25
- package/dist/model-manager-DgyKMjLN.mjs.map +1 -0
- package/dist/{node-BxujauDw.mjs → node-DUGiIkv1.mjs} +1 -1
- package/dist/{node-BxujauDw.mjs.map → node-DUGiIkv1.mjs.map} +1 -1
- package/dist/{ops-CGKIKwDc.mjs → ops-BzbThweu.mjs} +165 -26
- package/dist/ops-BzbThweu.mjs.map +1 -0
- package/dist/{overlay-reader-DNaVsgiS.mjs → overlay-reader-CkU_pGXZ.mjs} +1 -1
- package/dist/{overlay-reader-DNaVsgiS.mjs.map → overlay-reader-CkU_pGXZ.mjs.map} +1 -1
- package/dist/{paths-enT2coeX.mjs → paths-CdPPWpbG.mjs} +1 -1
- package/dist/{paths-enT2coeX.mjs.map → paths-CdPPWpbG.mjs.map} +1 -1
- package/dist/providers/github/index.d.mts +1 -1
- package/dist/providers/github/index.mjs +2 -2
- package/dist/providers/gitlab/index.d.mts +1 -1
- package/dist/providers/gitlab/index.mjs +2 -2
- package/dist/providers/local/index.d.mts +7 -1
- package/dist/providers/local/index.d.mts.map +1 -1
- package/dist/providers/local/index.mjs +11 -10
- package/dist/{scan-config-BlNLRCMx.mjs → scan-config-Dx49nCqk.mjs} +1 -1
- package/dist/{scan-config-BlNLRCMx.mjs.map → scan-config-Dx49nCqk.mjs.map} +1 -1
- package/dist/{scanner-CGWhmpDz.mjs → scanner-BSvXjfkp.mjs} +6 -6
- package/dist/{scanner-CGWhmpDz.mjs.map → scanner-BSvXjfkp.mjs.map} +1 -1
- package/dist/serialization-tmgfFBOh.mjs +2 -0
- package/dist/server/http/index.d.mts +1 -1
- package/dist/server/http/index.mjs +21 -20
- package/dist/server/http/index.mjs.map +1 -1
- package/dist/{server-DLauBiu2.d.mts → server-CLPNnhYL.d.mts} +2 -2
- package/dist/{server-DLauBiu2.d.mts.map → server-CLPNnhYL.d.mts.map} +1 -1
- package/dist/{server-CUZnuHQx.mjs → server-D-zJWbdI.mjs} +313 -115
- package/dist/server-D-zJWbdI.mjs.map +1 -0
- package/dist/server.d.mts +1 -1
- package/dist/server.mjs +21 -20
- package/dist/{source-map-generator-Bvbu-kM5.mjs → source-map-generator-DhjiQdV3.mjs} +1 -1
- package/dist/{source-map-generator-Bvbu-kM5.mjs.map → source-map-generator-DhjiQdV3.mjs.map} +1 -1
- package/dist/{svelte-parser-DaFK9iY0.mjs → svelte-parser-CwySu9yn.mjs} +3 -3
- package/dist/{svelte-parser-DaFK9iY0.mjs.map → svelte-parser-CwySu9yn.mjs.map} +1 -1
- package/dist/templates/index.mjs +1 -1
- package/dist/{templates-D6EllixQ.mjs → templates-CAkTIRkE.mjs} +430 -109
- package/dist/templates-CAkTIRkE.mjs.map +1 -0
- package/dist/testing/memory-provider.d.mts +52 -0
- package/dist/testing/memory-provider.d.mts.map +1 -0
- package/dist/testing/memory-provider.mjs +205 -0
- package/dist/testing/memory-provider.mjs.map +1 -0
- package/dist/tools/annotations.d.mts.map +1 -1
- package/dist/tools/annotations.mjs +1 -1
- package/dist/tools/availability.mjs +1 -1
- package/dist/{transaction-1SPznNt3.mjs → transaction-BW9ektWu.mjs} +13 -12
- package/dist/transaction-BW9ektWu.mjs.map +1 -0
- package/dist/{tsx-parser-md1N0Niu.mjs → tsx-parser-BvsK6lyG.mjs} +1 -1
- package/dist/{tsx-parser-md1N0Niu.mjs.map → tsx-parser-BvsK6lyG.mjs.map} +1 -1
- package/dist/tsx-parser-Byc7HlJ8.mjs +2 -0
- package/dist/util/detect.mjs +1 -1
- package/dist/util/fs.d.mts +1 -1
- package/dist/util/fs.d.mts.map +1 -1
- package/dist/util/id.mjs +1 -1
- package/dist/{validator-ChiYk6ap.mjs → validator-0sJ9vh1j.mjs} +65 -8
- package/dist/validator-0sJ9vh1j.mjs.map +1 -0
- package/dist/{vue-parser-CHxDohd8.mjs → vue-parser-auedZiJG.mjs} +3 -3
- package/dist/{vue-parser-CHxDohd8.mjs.map → vue-parser-auedZiJG.mjs.map} +1 -1
- package/package.json +13 -5
- package/testing/conformance/01-new-collection-entry/expected/.contentrain/meta/blog/en.json +1 -0
- package/testing/conformance/01-new-collection-entry/expected/.contentrain/models/blog.json +6 -5
- package/testing/conformance/01-new-collection-entry/setup/.contentrain/models/blog.json +6 -5
- package/testing/conformance/02-new-singleton/expected/.contentrain/meta/hero/en.json +1 -0
- package/testing/conformance/02-new-singleton/expected/.contentrain/models/hero.json +6 -5
- package/testing/conformance/02-new-singleton/setup/.contentrain/models/hero.json +6 -5
- package/testing/conformance/03-new-dictionary/expected/.contentrain/meta/ui-strings/en.json +1 -0
- package/testing/conformance/03-new-dictionary/expected/.contentrain/models/ui-strings.json +4 -3
- package/testing/conformance/03-new-dictionary/setup/.contentrain/models/ui-strings.json +4 -3
- package/dist/annotations-D3tlsF38.mjs.map +0 -1
- package/dist/apply-manager-SLCRLHN_.mjs.map +0 -1
- package/dist/branch-lifecycle-BAfgSQBv.mjs.map +0 -1
- package/dist/content-manager-DWo3G64y.d.mts.map +0 -1
- package/dist/doctor-BwJ_nmqS.mjs.map +0 -1
- package/dist/local-8dJ5NIWL.mjs.map +0 -1
- package/dist/meta-manager-CJUiTgP2.mjs.map +0 -1
- package/dist/model-manager-DP2CZiMT.mjs.map +0 -1
- package/dist/ops-CGKIKwDc.mjs.map +0 -1
- package/dist/serialization-B1CEzR4H.mjs +0 -2
- package/dist/server-CUZnuHQx.mjs.map +0 -1
- package/dist/templates-D6EllixQ.mjs.map +0 -1
- package/dist/transaction-1SPznNt3.mjs.map +0 -1
- package/dist/tsx-parser-C3XsIrwU.mjs +0 -2
- package/dist/validator-ChiYk6ap.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates-CAkTIRkE.mjs","names":[],"sources":["../src/templates/blog.ts","../src/templates/landing.ts","../src/templates/docs.ts","../src/templates/ecommerce.ts","../src/templates/saas.ts","../src/templates/i18n.ts","../src/templates/mobile.ts","../src/templates/index.ts"],"sourcesContent":["import type { ScaffoldTemplate } from '@contentrain/types'\n\nexport const blogTemplate: ScaffoldTemplate = {\n id: 'blog',\n models: [\n {\n id: 'blog-post',\n name: 'Blog Post',\n kind: 'document',\n domain: 'blog',\n i18n: true,\n title_field: 'title',\n fields: {\n title: { type: 'string', required: true, max: 120, label: 'Title', order: 10 },\n slug: { type: 'slug', required: true, unique: true, label: 'Slug', order: 20 },\n excerpt: { type: 'text', max: 280, label: 'Excerpt', order: 30 },\n cover: { type: 'image', label: 'Cover', order: 40 },\n author: { type: 'relation', model: 'authors', required: true, label: 'Author', order: 50 },\n category: { type: 'relation', model: 'categories', label: 'Category', order: 60 },\n tags: { type: 'array', items: 'string', label: 'Tags', order: 70 },\n published_at: { type: 'datetime', label: 'Published at', order: 80 },\n },\n },\n {\n id: 'categories',\n name: 'Categories',\n kind: 'collection',\n domain: 'blog',\n i18n: true,\n title_field: 'name',\n fields: {\n name: { type: 'string', required: true, label: 'Name', order: 10 },\n slug: { type: 'slug', required: true, unique: true, label: 'Slug', order: 20 },\n description: { type: 'text', label: 'Description', order: 30 },\n },\n },\n {\n id: 'authors',\n name: 'Authors',\n kind: 'collection',\n domain: 'blog',\n i18n: false,\n title_field: 'name',\n fields: {\n name: { type: 'string', required: true, label: 'Name', order: 10 },\n email: { type: 'email', label: 'Email', order: 20 },\n bio: { type: 'text', label: 'Bio', order: 30 },\n avatar: { type: 'image', label: 'Avatar', order: 40 },\n social: {\n type: 'object',\n fields: {\n twitter: { type: 'url', label: 'Twitter', order: 10 },\n linkedin: { type: 'url', label: 'Linkedin', order: 20 },\n github: { type: 'url', label: 'Github', order: 30 },\n },\n },\n },\n },\n ],\n sample_content: {\n categories: {\n en: {\n 'cat-engineering': { name: 'Engineering', slug: 'engineering', description: 'Technical deep dives' },\n 'cat-product': { name: 'Product', slug: 'product', description: 'Product updates and news' },\n },\n },\n authors: {\n data: {\n 'author-1': { name: 'Team', email: 'team@example.com', bio: 'The team behind the product' },\n },\n },\n },\n vocabulary: {\n 'read-more': { en: 'Read More' },\n 'published-on': { en: 'Published on' },\n 'by-author': { en: 'By' },\n 'categories': { en: 'Categories' },\n 'tags': { en: 'Tags' },\n },\n}\n","import type { ScaffoldTemplate } from '@contentrain/types'\n\nexport const landingTemplate: ScaffoldTemplate = {\n id: 'landing',\n models: [\n {\n id: 'hero',\n name: 'Hero Section',\n kind: 'singleton',\n domain: 'marketing',\n i18n: true,\n title_field: 'title',\n fields: {\n title: { type: 'string', required: true, max: 120, label: 'Title', order: 10 },\n subtitle: { type: 'text', max: 280, label: 'Subtitle', order: 20 },\n cta_text: { type: 'string', label: 'CTA text', order: 30 },\n cta_url: { type: 'url', label: 'CTA URL', order: 40 },\n background: { type: 'image', label: 'Background', order: 50 },\n },\n },\n {\n id: 'features',\n name: 'Features Section',\n kind: 'singleton',\n domain: 'marketing',\n i18n: true,\n title_field: 'title',\n fields: {\n title: { type: 'string', required: true, label: 'Title', order: 10 },\n subtitle: { type: 'text', label: 'Subtitle', order: 20 },\n items: {\n type: 'array',\n items: {\n type: 'object',\n fields: {\n title: { type: 'string', required: true, label: 'Title', order: 10 },\n description: { type: 'text', label: 'Description', order: 20 },\n icon: { type: 'icon', label: 'Icon', order: 30 },\n },\n },\n },\n },\n },\n {\n id: 'pricing',\n name: 'Pricing Plans',\n kind: 'collection',\n domain: 'marketing',\n i18n: true,\n title_field: 'name',\n fields: {\n name: { type: 'string', required: true, label: 'Name', order: 10 },\n price: { type: 'number', required: true, label: 'Price', order: 20 },\n currency: { type: 'string', default: 'USD', label: 'Currency', order: 30 },\n period: { type: 'select', options: ['monthly', 'yearly'], label: 'Period', order: 40 },\n features: { type: 'array', items: 'string', label: 'Features', order: 50 },\n highlighted: { type: 'boolean', default: false, label: 'Highlighted', order: 60 },\n },\n },\n {\n id: 'faq',\n name: 'FAQ',\n kind: 'collection',\n domain: 'marketing',\n i18n: true,\n title_field: 'question',\n fields: {\n question: { type: 'string', required: true, label: 'Question', order: 10 },\n answer: { type: 'text', required: true, label: 'Answer', order: 20 },\n order: { type: 'integer', label: 'Order', order: 30 },\n },\n },\n {\n id: 'testimonials',\n name: 'Testimonials',\n kind: 'collection',\n domain: 'marketing',\n i18n: true,\n title_field: 'name',\n fields: {\n name: { type: 'string', required: true, label: 'Name', order: 10 },\n role: { type: 'string', label: 'Role', order: 20 },\n company: { type: 'string', label: 'Company', order: 30 },\n quote: { type: 'text', required: true, label: 'Quote', order: 40 },\n avatar: { type: 'image', label: 'Avatar', order: 50 },\n rating: { type: 'rating', label: 'Rating', order: 60 },\n },\n },\n ],\n sample_content: {\n hero: {\n en: {\n title: 'Build something amazing',\n subtitle: 'The fastest way to launch your product',\n cta_text: 'Get Started',\n cta_url: '/signup',\n },\n },\n pricing: {\n en: {\n 'plan-free': { name: 'Free', price: 0, currency: 'USD', period: 'monthly', features: ['1 project', 'Basic support'], highlighted: false },\n 'plan-pro': { name: 'Pro', price: 29, currency: 'USD', period: 'monthly', features: ['Unlimited projects', 'Priority support', 'Advanced analytics'], highlighted: true },\n },\n },\n },\n vocabulary: {\n 'get-started': { en: 'Get Started' },\n 'learn-more': { en: 'Learn More' },\n 'per-month': { en: '/month' },\n 'most-popular': { en: 'Most Popular' },\n },\n}\n","import type { ScaffoldTemplate } from '@contentrain/types'\n\nexport const docsTemplate: ScaffoldTemplate = {\n id: 'docs',\n models: [\n {\n id: 'doc-page',\n name: 'Documentation Page',\n kind: 'document',\n domain: 'docs',\n i18n: true,\n title_field: 'title',\n fields: {\n title: { type: 'string', required: true, max: 120, label: 'Title', order: 10 },\n slug: { type: 'slug', required: true, unique: true, label: 'Slug', order: 20 },\n category: { type: 'relation', model: 'doc-categories', label: 'Category', order: 30 },\n order: { type: 'integer', label: 'Order', order: 40 },\n excerpt: { type: 'text', max: 280, label: 'Excerpt', order: 50 },\n },\n },\n {\n id: 'doc-categories',\n name: 'Doc Categories',\n kind: 'collection',\n domain: 'docs',\n i18n: true,\n title_field: 'name',\n fields: {\n name: { type: 'string', required: true, label: 'Name', order: 10 },\n slug: { type: 'slug', required: true, unique: true, label: 'Slug', order: 20 },\n order: { type: 'integer', label: 'Order', order: 30 },\n },\n },\n ],\n sample_content: {\n 'doc-categories': {\n en: {\n 'cat-getting-started': { name: 'Getting Started', slug: 'getting-started', order: 1 },\n 'cat-guides': { name: 'Guides', slug: 'guides', order: 2 },\n 'cat-api': { name: 'API Reference', slug: 'api', order: 3 },\n },\n },\n },\n vocabulary: {\n 'on-this-page': { en: 'On This Page' },\n 'edit-this-page': { en: 'Edit this page' },\n 'next': { en: 'Next' },\n 'previous': { en: 'Previous' },\n },\n}\n","import type { ScaffoldTemplate } from '@contentrain/types'\n\nexport const ecommerceTemplate: ScaffoldTemplate = {\n id: 'ecommerce',\n models: [\n {\n id: 'products',\n name: 'Products',\n kind: 'collection',\n domain: 'shop',\n i18n: true,\n title_field: 'name',\n fields: {\n name: { type: 'string', required: true, label: 'Name', order: 10 },\n slug: { type: 'slug', required: true, unique: true, label: 'Slug', order: 20 },\n description: { type: 'text', label: 'Description', order: 30 },\n price: { type: 'decimal', required: true, label: 'Price', order: 40 },\n currency: { type: 'string', default: 'USD', label: 'Currency', order: 50 },\n image: { type: 'image', label: 'Image', order: 60 },\n category: { type: 'relation', model: 'product-categories', label: 'Category', order: 70 },\n brand: { type: 'relation', model: 'brands', label: 'Brand', order: 80 },\n in_stock: { type: 'boolean', default: true, label: 'In stock', order: 90 },\n },\n },\n {\n id: 'product-categories',\n name: 'Product Categories',\n kind: 'collection',\n domain: 'shop',\n i18n: true,\n title_field: 'name',\n fields: {\n name: { type: 'string', required: true, label: 'Name', order: 10 },\n slug: { type: 'slug', required: true, unique: true, label: 'Slug', order: 20 },\n description: { type: 'text', label: 'Description', order: 30 },\n image: { type: 'image', label: 'Image', order: 40 },\n },\n },\n {\n id: 'brands',\n name: 'Brands',\n kind: 'collection',\n domain: 'shop',\n i18n: false,\n title_field: 'name',\n fields: {\n name: { type: 'string', required: true, label: 'Name', order: 10 },\n slug: { type: 'slug', required: true, unique: true, label: 'Slug', order: 20 },\n logo: { type: 'image', label: 'Logo', order: 30 },\n website: { type: 'url', label: 'Website', order: 40 },\n },\n },\n ],\n sample_content: {\n 'product-categories': {\n en: {\n 'cat-electronics': { name: 'Electronics', slug: 'electronics', description: 'Electronic devices and accessories' },\n 'cat-clothing': { name: 'Clothing', slug: 'clothing', description: 'Apparel and fashion' },\n },\n },\n brands: {\n data: {\n 'brand-acme': { name: 'Acme Corp', slug: 'acme', website: 'https://example.com' },\n },\n },\n },\n vocabulary: {\n 'add-to-cart': { en: 'Add to Cart' },\n 'out-of-stock': { en: 'Out of Stock' },\n 'price': { en: 'Price' },\n 'category': { en: 'Category' },\n },\n}\n","import type { ScaffoldTemplate } from '@contentrain/types'\n\nexport const saasTemplate: ScaffoldTemplate = {\n id: 'saas',\n models: [\n {\n id: 'hero',\n name: 'Hero Section',\n kind: 'singleton',\n domain: 'marketing',\n i18n: true,\n title_field: 'title',\n fields: {\n title: { type: 'string', required: true, max: 120, label: 'Title', order: 10 },\n subtitle: { type: 'text', max: 280, label: 'Subtitle', order: 20 },\n cta_text: { type: 'string', label: 'CTA text', order: 30 },\n cta_url: { type: 'url', label: 'CTA URL', order: 40 },\n background: { type: 'image', label: 'Background', order: 50 },\n },\n },\n {\n id: 'features',\n name: 'Features',\n kind: 'singleton',\n domain: 'marketing',\n i18n: true,\n title_field: 'title',\n fields: {\n title: { type: 'string', required: true, label: 'Title', order: 10 },\n subtitle: { type: 'text', label: 'Subtitle', order: 20 },\n items: {\n type: 'array',\n items: {\n type: 'object',\n fields: {\n title: { type: 'string', required: true, label: 'Title', order: 10 },\n description: { type: 'text', label: 'Description', order: 20 },\n icon: { type: 'icon', label: 'Icon', order: 30 },\n },\n },\n },\n },\n },\n {\n id: 'pricing',\n name: 'Pricing Plans',\n kind: 'collection',\n domain: 'marketing',\n i18n: true,\n title_field: 'name',\n fields: {\n name: { type: 'string', required: true, label: 'Name', order: 10 },\n price: { type: 'number', required: true, label: 'Price', order: 20 },\n currency: { type: 'string', default: 'USD', label: 'Currency', order: 30 },\n period: { type: 'select', options: ['monthly', 'yearly'], label: 'Period', order: 40 },\n features: { type: 'array', items: 'string', label: 'Features', order: 50 },\n highlighted: { type: 'boolean', default: false, label: 'Highlighted', order: 60 },\n },\n },\n {\n id: 'changelog',\n name: 'Changelog',\n kind: 'document',\n domain: 'product',\n i18n: true,\n title_field: 'title',\n fields: {\n title: { type: 'string', required: true, label: 'Title', order: 10 },\n slug: { type: 'slug', required: true, unique: true, label: 'Slug', order: 20 },\n version: { type: 'string', label: 'Version', order: 30 },\n published_at: { type: 'date', label: 'Published at', order: 40 },\n tags: { type: 'array', items: 'string', label: 'Tags', order: 50 },\n },\n },\n {\n id: 'faq',\n name: 'FAQ',\n kind: 'collection',\n domain: 'marketing',\n i18n: true,\n title_field: 'question',\n fields: {\n question: { type: 'string', required: true, label: 'Question', order: 10 },\n answer: { type: 'text', required: true, label: 'Answer', order: 20 },\n order: { type: 'integer', label: 'Order', order: 30 },\n },\n },\n ],\n sample_content: {\n hero: {\n en: {\n title: 'Ship faster with our platform',\n subtitle: 'Everything you need to build, deploy, and scale',\n cta_text: 'Start Free Trial',\n cta_url: '/signup',\n },\n },\n pricing: {\n en: {\n 'plan-starter': { name: 'Starter', price: 0, currency: 'USD', period: 'monthly', features: ['Up to 3 projects', 'Community support'], highlighted: false },\n 'plan-team': { name: 'Team', price: 49, currency: 'USD', period: 'monthly', features: ['Unlimited projects', 'Priority support', 'Team collaboration'], highlighted: true },\n 'plan-enterprise': { name: 'Enterprise', price: 199, currency: 'USD', period: 'monthly', features: ['Everything in Team', 'SSO', 'SLA', 'Dedicated support'], highlighted: false },\n },\n },\n },\n vocabulary: {\n 'start-free': { en: 'Start Free Trial' },\n 'contact-sales': { en: 'Contact Sales' },\n 'per-month': { en: '/month' },\n 'changelog': { en: 'Changelog' },\n },\n}\n","import type { ScaffoldTemplate } from '@contentrain/types'\n\nexport const i18nTemplate: ScaffoldTemplate = {\n id: 'i18n',\n models: [\n {\n id: 'navigation',\n name: 'Navigation',\n kind: 'singleton',\n domain: 'ui',\n i18n: true,\n title_field: 'brand',\n fields: {\n brand: { type: 'string', required: true, label: 'Brand', order: 10 },\n items: {\n type: 'array',\n items: {\n type: 'object',\n fields: {\n label: { type: 'string', required: true, label: 'Label', order: 10 },\n url: { type: 'url', required: true, label: 'URL', order: 20 },\n },\n },\n },\n },\n },\n {\n id: 'form-labels',\n name: 'Form Labels',\n kind: 'singleton',\n domain: 'ui',\n i18n: true,\n title_field: 'name',\n fields: {\n name: { type: 'string', label: 'Name', order: 10 },\n email: { type: 'string', label: 'Email', order: 20 },\n password: { type: 'string', label: 'Password', order: 30 },\n submit: { type: 'string', label: 'Submit', order: 40 },\n cancel: { type: 'string', label: 'Cancel', order: 50 },\n },\n },\n {\n id: 'error-messages',\n name: 'Error Messages',\n kind: 'dictionary',\n domain: 'system',\n i18n: true,\n title_field: 'key',\n },\n {\n id: 'app-strings',\n name: 'App Strings',\n kind: 'dictionary',\n domain: 'system',\n i18n: true,\n title_field: 'key',\n },\n ],\n sample_content: {\n navigation: {\n en: {\n brand: 'My App',\n items: [\n { label: 'Home', url: '/' },\n { label: 'About', url: '/about' },\n { label: 'Contact', url: '/contact' },\n ],\n },\n },\n 'form-labels': {\n en: {\n name: 'Name',\n email: 'Email',\n password: 'Password',\n submit: 'Submit',\n cancel: 'Cancel',\n },\n },\n 'error-messages': {\n en: {\n 'required-field': 'This field is required',\n 'invalid-email': 'Please enter a valid email',\n 'server-error': 'Something went wrong, please try again',\n },\n },\n 'app-strings': {\n en: {\n 'welcome': 'Welcome',\n 'loading': 'Loading...',\n 'no-results': 'No results found',\n },\n },\n },\n vocabulary: {\n 'language': { en: 'Language' },\n 'switch-locale': { en: 'Switch language' },\n },\n}\n","import type { ScaffoldTemplate } from '@contentrain/types'\n\nexport const mobileTemplate: ScaffoldTemplate = {\n id: 'mobile',\n models: [\n {\n id: 'app-strings',\n name: 'App Strings',\n kind: 'dictionary',\n domain: 'system',\n i18n: true,\n title_field: 'key',\n },\n {\n id: 'error-messages',\n name: 'Error Messages',\n kind: 'dictionary',\n domain: 'system',\n i18n: true,\n title_field: 'key',\n },\n {\n id: 'onboarding',\n name: 'Onboarding Screens',\n kind: 'collection',\n domain: 'app',\n i18n: true,\n title_field: 'title',\n fields: {\n title: { type: 'string', required: true, label: 'Title', order: 10 },\n description: { type: 'text', label: 'Description', order: 20 },\n image: { type: 'image', label: 'Image', order: 30 },\n order: { type: 'integer', required: true, label: 'Order', order: 40 },\n },\n },\n ],\n sample_content: {\n 'app-strings': {\n en: {\n 'welcome': 'Welcome',\n 'loading': 'Loading...',\n 'retry': 'Retry',\n 'ok': 'OK',\n 'cancel': 'Cancel',\n },\n },\n 'error-messages': {\n en: {\n 'network-error': 'No internet connection',\n 'server-error': 'Something went wrong',\n 'auth-error': 'Please sign in again',\n },\n },\n onboarding: {\n en: {\n 'step-1': { title: 'Welcome', description: 'Get started with our app', order: 1 },\n 'step-2': { title: 'Discover', description: 'Explore all features', order: 2 },\n 'step-3': { title: 'Ready', description: 'You are all set!', order: 3 },\n },\n },\n },\n vocabulary: {\n 'get-started': { en: 'Get Started' },\n 'skip': { en: 'Skip' },\n 'next': { en: 'Next' },\n },\n}\n","import type { ScaffoldTemplate } from '@contentrain/types'\nimport { blogTemplate } from './blog.js'\nimport { landingTemplate } from './landing.js'\nimport { docsTemplate } from './docs.js'\nimport { ecommerceTemplate } from './ecommerce.js'\nimport { saasTemplate } from './saas.js'\nimport { i18nTemplate } from './i18n.js'\nimport { mobileTemplate } from './mobile.js'\n\nconst templates: Record<string, ScaffoldTemplate> = {\n blog: blogTemplate,\n landing: landingTemplate,\n docs: docsTemplate,\n ecommerce: ecommerceTemplate,\n saas: saasTemplate,\n i18n: i18nTemplate,\n mobile: mobileTemplate,\n}\n\nexport function getTemplate(id: string): ScaffoldTemplate | null {\n return templates[id] ?? null\n}\n\nexport function listTemplates(): string[] {\n return Object.keys(templates)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { a as FileChange, g as RepoProvider, h as ProviderCapabilities, m as MergeResult, n as Branch, o as FileDiff, r as Commit, t as ApplyPlanInput, y as WriteReadiness } from "../index-BWbTJlXm.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/testing/memory-provider.d.ts
|
|
4
|
+
interface MemoryProviderOptions {
|
|
5
|
+
/** Seed files, keyed by repo-relative path. */
|
|
6
|
+
files?: Record<string, string>;
|
|
7
|
+
/** Override the capability set — e.g. to exercise the remote-provider path. */
|
|
8
|
+
capabilities?: Partial<ProviderCapabilities>;
|
|
9
|
+
/** Refuse writes, as a local repo does under branch pressure. */
|
|
10
|
+
writeReadiness?: WriteReadiness;
|
|
11
|
+
}
|
|
12
|
+
/** A commit as recorded by the provider, with the changes that produced it. */
|
|
13
|
+
interface RecordedCommit extends Commit {
|
|
14
|
+
branch: string;
|
|
15
|
+
changes: FileChange[];
|
|
16
|
+
}
|
|
17
|
+
declare class MemoryProvider implements RepoProvider {
|
|
18
|
+
readonly capabilities: ProviderCapabilities;
|
|
19
|
+
/** Every commit in order, with its changes — the assertion surface. */
|
|
20
|
+
readonly commits: RecordedCommit[];
|
|
21
|
+
private readonly refs;
|
|
22
|
+
private writeReadiness;
|
|
23
|
+
private sequence;
|
|
24
|
+
constructor(options?: MemoryProviderOptions);
|
|
25
|
+
/** Whole tree of a ref as a plain object — snapshot-friendly. */
|
|
26
|
+
snapshot(ref?: string): Record<string, string>;
|
|
27
|
+
/** Every path on a ref, sorted. */
|
|
28
|
+
paths(ref?: string): string[];
|
|
29
|
+
/** Seed or overwrite a file without going through a commit. */
|
|
30
|
+
seed(path: string, content: string, ref?: string): void;
|
|
31
|
+
/** Start refusing (or accepting) writes, as branch pressure would. */
|
|
32
|
+
setWriteReadiness(readiness: WriteReadiness): void;
|
|
33
|
+
readFile(path: string, ref?: string): Promise<string>;
|
|
34
|
+
listDirectory(path: string, ref?: string): Promise<string[]>;
|
|
35
|
+
fileExists(path: string, ref?: string): Promise<boolean>;
|
|
36
|
+
checkWriteReadiness(): Promise<WriteReadiness>;
|
|
37
|
+
applyPlan(input: ApplyPlanInput): Promise<Commit>;
|
|
38
|
+
listBranches(prefix?: string): Promise<Branch[]>;
|
|
39
|
+
createBranch(name: string, fromRef?: string): Promise<void>;
|
|
40
|
+
deleteBranch(name: string): Promise<void>;
|
|
41
|
+
getBranchDiff(branch: string, base?: string): Promise<FileDiff[]>;
|
|
42
|
+
mergeBranch(branch: string, into: string, opts?: {
|
|
43
|
+
removeSourceBranch?: boolean;
|
|
44
|
+
}): Promise<MergeResult>;
|
|
45
|
+
isMerged(branch: string, into?: string): Promise<boolean>;
|
|
46
|
+
getDefaultBranch(): Promise<string>;
|
|
47
|
+
private tree;
|
|
48
|
+
private readWorkflow;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { MemoryProvider, MemoryProviderOptions, RecordedCommit };
|
|
52
|
+
//# sourceMappingURL=memory-provider.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-provider.d.mts","names":[],"sources":["../../src/testing/memory-provider.ts"],"mappings":";;;UAuDiB,qBAAA;;EAEf,KAAA,GAAQ,MAAA;EAF4B;EAIpC,YAAA,GAAe,OAAA,CAAQ,oBAAA;EAFf;EAIR,cAAA,GAAiB,cAAA;AAAA;;UAIF,cAAA,SAAuB,MAAA;EACtC,MAAA;EACA,OAAA,EAAS,UAAA;AAAA;AAAA,cAKE,cAAA,YAA0B,YAAA;EAAA,SAC5B,YAAA,EAAc,oBAAA;EAdA;EAAA,SAiBd,OAAA,EAAS,cAAA;EAAA,iBAED,IAAA;EAAA,QACT,cAAA;EAAA,QACA,QAAA;cAEI,OAAA,GAAS,qBAAA;EAjBS;EA0B9B,QAAA,CAAS,GAAA,YAAmC,MAAA;EA1BA;EAiC5C,KAAA,CAAM,GAAA;EAhCN;EAqCA,IAAA,CAAK,IAAA,UAAc,OAAA,UAAiB,GAAA;EApC3B;EAyCT,iBAAA,CAAkB,SAAA,EAAW,cAAA;EAM7B,QAAA,CAAS,IAAA,UAAc,GAAA,YAAe,OAAA;EAQtC,aAAA,CAAc,IAAA,UAAc,GAAA,YAAe,OAAA;EAa3C,UAAA,CAAW,IAAA,UAAc,GAAA,YAAe,OAAA;EAcxC,mBAAA,CAAA,GAAuB,OAAA,CAAQ,cAAA;EAIzB,SAAA,CAAU,KAAA,EAAO,cAAA,GAAiB,OAAA,CAAQ,MAAA;EAkChD,YAAA,CAAa,MAAA,YAAkB,OAAA,CAAQ,MAAA;EAQvC,YAAA,CAAa,IAAA,UAAc,OAAA,YAAmB,OAAA;EAK9C,YAAA,CAAa,IAAA,WAAe,OAAA;EAK5B,aAAA,CAAc,MAAA,UAAgB,IAAA,YAAgB,OAAA,CAAQ,QAAA;EAetD,WAAA,CAAY,MAAA,UAAgB,IAAA,UAAc,IAAA;IAAS,kBAAA;EAAA,IAAiC,OAAA,CAAQ,WAAA;EAY5F,QAAA,CAAS,MAAA,UAAgB,IAAA,YAAgB,OAAA;EAUzC,gBAAA,CAAA,GAAoB,OAAA;EAAA,QAMZ,IAAA;EAAA,QASM,YAAA;AAAA"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { CONTENTRAIN_BRANCH } from "@contentrain/types";
|
|
2
|
+
//#region src/testing/memory-provider.ts
|
|
3
|
+
/**
|
|
4
|
+
* MemoryProvider — a complete `RepoProvider` backed by `Map`s.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the write-side tool tests were spending their entire
|
|
7
|
+
* wall-clock in `posix_spawn`. A `contentrain_init` costs 33 git subprocesses
|
|
8
|
+
* and a content save 26, and on macOS each one is dominated by fork cost
|
|
9
|
+
* rather than by git. Tests that assert "does model_save reject an invalid
|
|
10
|
+
* field type" were paying for worktree creation, merge and ref surgery to
|
|
11
|
+
* find out.
|
|
12
|
+
*
|
|
13
|
+
* None of that is what those tests are about. The provider contract is the
|
|
14
|
+
* seam the engine was already built around — `@contentrain/types` names "a
|
|
15
|
+
* mock in a test suite" as a legitimate implementation — so this fills it in
|
|
16
|
+
* and the tool layer runs unchanged, with zero subprocesses.
|
|
17
|
+
*
|
|
18
|
+
* What it is faithful about, because tests depend on it:
|
|
19
|
+
* - branches are real snapshots, forked from `base` at fork time, so a
|
|
20
|
+
* write to a `cr/*` branch is invisible on `contentrain` until it merges
|
|
21
|
+
* - the workflow decision reads the project's own `config.json`, so
|
|
22
|
+
* auto-merge and review are both genuinely exercised
|
|
23
|
+
* - `listDirectory` returns immediate children only, and directories
|
|
24
|
+
* without a trailing entry still report as existing, matching `readdir`
|
|
25
|
+
*
|
|
26
|
+
* What it deliberately does not model: conflicts, worktrees, selective sync
|
|
27
|
+
* into a developer's working tree, and anything else whose behaviour is a
|
|
28
|
+
* property of git rather than of Contentrain. A test that needs those needs a
|
|
29
|
+
* real repository — keep those, and only those, on the git fixture path.
|
|
30
|
+
*/
|
|
31
|
+
const MEMORY_CAPABILITIES = {
|
|
32
|
+
localWorktree: true,
|
|
33
|
+
sourceRead: true,
|
|
34
|
+
sourceWrite: true,
|
|
35
|
+
pushRemote: false,
|
|
36
|
+
branchProtection: false,
|
|
37
|
+
pullRequestFallback: false,
|
|
38
|
+
astScan: false
|
|
39
|
+
};
|
|
40
|
+
var MemoryProvider = class {
|
|
41
|
+
capabilities;
|
|
42
|
+
/** Every commit in order, with its changes — the assertion surface. */
|
|
43
|
+
commits = [];
|
|
44
|
+
refs = /* @__PURE__ */ new Map();
|
|
45
|
+
writeReadiness;
|
|
46
|
+
sequence = 0;
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
this.capabilities = {
|
|
49
|
+
...MEMORY_CAPABILITIES,
|
|
50
|
+
...options.capabilities
|
|
51
|
+
};
|
|
52
|
+
this.writeReadiness = options.writeReadiness ?? { blocked: false };
|
|
53
|
+
this.refs.set(CONTENTRAIN_BRANCH, new Map(Object.entries(options.files ?? {})));
|
|
54
|
+
}
|
|
55
|
+
/** Whole tree of a ref as a plain object — snapshot-friendly. */
|
|
56
|
+
snapshot(ref = CONTENTRAIN_BRANCH) {
|
|
57
|
+
return Object.fromEntries([...this.refs.get(ref) ?? /* @__PURE__ */ new Map()].toSorted(([a], [b]) => a.localeCompare(b, "en")));
|
|
58
|
+
}
|
|
59
|
+
/** Every path on a ref, sorted. */
|
|
60
|
+
paths(ref = CONTENTRAIN_BRANCH) {
|
|
61
|
+
return [...(this.refs.get(ref) ?? /* @__PURE__ */ new Map()).keys()].toSorted((a, b) => a.localeCompare(b, "en"));
|
|
62
|
+
}
|
|
63
|
+
/** Seed or overwrite a file without going through a commit. */
|
|
64
|
+
seed(path, content, ref = CONTENTRAIN_BRANCH) {
|
|
65
|
+
this.tree(ref).set(path, content);
|
|
66
|
+
}
|
|
67
|
+
/** Start refusing (or accepting) writes, as branch pressure would. */
|
|
68
|
+
setWriteReadiness(readiness) {
|
|
69
|
+
this.writeReadiness = readiness;
|
|
70
|
+
}
|
|
71
|
+
readFile(path, ref) {
|
|
72
|
+
const content = this.tree(ref ?? CONTENTRAIN_BRANCH).get(normalize(path));
|
|
73
|
+
if (content === void 0) return Promise.reject(/* @__PURE__ */ new Error(`ENOENT: no such file, open '${path}'`));
|
|
74
|
+
return Promise.resolve(content);
|
|
75
|
+
}
|
|
76
|
+
listDirectory(path, ref) {
|
|
77
|
+
const prefix = normalize(path) === "" ? "" : `${normalize(path)}/`;
|
|
78
|
+
const names = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const key of this.tree(ref ?? CONTENTRAIN_BRANCH).keys()) {
|
|
80
|
+
if (!key.startsWith(prefix)) continue;
|
|
81
|
+
const rest = key.slice(prefix.length);
|
|
82
|
+
if (rest === "") continue;
|
|
83
|
+
names.add(rest.split("/")[0]);
|
|
84
|
+
}
|
|
85
|
+
return Promise.resolve([...names].toSorted((a, b) => a.localeCompare(b, "en")));
|
|
86
|
+
}
|
|
87
|
+
fileExists(path, ref) {
|
|
88
|
+
const target = normalize(path);
|
|
89
|
+
const tree = this.tree(ref ?? CONTENTRAIN_BRANCH);
|
|
90
|
+
if (tree.has(target)) return Promise.resolve(true);
|
|
91
|
+
const prefix = `${target}/`;
|
|
92
|
+
for (const key of tree.keys()) if (key.startsWith(prefix)) return Promise.resolve(true);
|
|
93
|
+
return Promise.resolve(false);
|
|
94
|
+
}
|
|
95
|
+
checkWriteReadiness() {
|
|
96
|
+
return Promise.resolve(this.writeReadiness);
|
|
97
|
+
}
|
|
98
|
+
async applyPlan(input) {
|
|
99
|
+
const base = input.base ?? CONTENTRAIN_BRANCH;
|
|
100
|
+
const tree = new Map(this.refs.get(input.branch) ?? this.tree(base));
|
|
101
|
+
for (const change of input.changes) if (change.content === null) tree.delete(normalize(change.path));
|
|
102
|
+
else tree.set(normalize(change.path), change.content);
|
|
103
|
+
this.refs.set(input.branch, tree);
|
|
104
|
+
const workflow = input.workflowOverride ?? await this.readWorkflow(base);
|
|
105
|
+
const autoMerge = this.capabilities.localWorktree && workflow === "auto-merge";
|
|
106
|
+
if (autoMerge) this.refs.set(base, new Map(tree));
|
|
107
|
+
const commit = {
|
|
108
|
+
sha: `mem${(++this.sequence).toString().padStart(9, "0")}`,
|
|
109
|
+
message: input.message,
|
|
110
|
+
author: input.author,
|
|
111
|
+
timestamp: new Date(Date.UTC(2026, 0, 1, 0, 0, this.sequence)).toISOString(),
|
|
112
|
+
branch: input.branch,
|
|
113
|
+
changes: input.changes,
|
|
114
|
+
...this.capabilities.localWorktree ? { workflowAction: autoMerge ? "auto-merged" : "pending-review" } : {}
|
|
115
|
+
};
|
|
116
|
+
this.commits.push(commit);
|
|
117
|
+
return commit;
|
|
118
|
+
}
|
|
119
|
+
listBranches(prefix) {
|
|
120
|
+
const names = [...this.refs.keys()].filter((n) => !prefix || n.startsWith(prefix));
|
|
121
|
+
return Promise.resolve(names.toSorted((a, b) => a.localeCompare(b, "en")).map((name) => ({
|
|
122
|
+
name,
|
|
123
|
+
sha: this.commits.findLast((c) => c.branch === name)?.sha ?? "mem000000000"
|
|
124
|
+
})));
|
|
125
|
+
}
|
|
126
|
+
createBranch(name, fromRef) {
|
|
127
|
+
this.refs.set(name, new Map(this.tree(fromRef ?? CONTENTRAIN_BRANCH)));
|
|
128
|
+
return Promise.resolve();
|
|
129
|
+
}
|
|
130
|
+
deleteBranch(name) {
|
|
131
|
+
this.refs.delete(name);
|
|
132
|
+
return Promise.resolve();
|
|
133
|
+
}
|
|
134
|
+
getBranchDiff(branch, base) {
|
|
135
|
+
const head = this.tree(branch);
|
|
136
|
+
const root = this.tree(base ?? CONTENTRAIN_BRANCH);
|
|
137
|
+
const diffs = [];
|
|
138
|
+
for (const [path, after] of head) {
|
|
139
|
+
const before = root.get(path) ?? null;
|
|
140
|
+
if (before === after) continue;
|
|
141
|
+
diffs.push({
|
|
142
|
+
path,
|
|
143
|
+
status: before === null ? "added" : "modified",
|
|
144
|
+
before,
|
|
145
|
+
after
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
for (const [path, before] of root) if (!head.has(path)) diffs.push({
|
|
149
|
+
path,
|
|
150
|
+
status: "removed",
|
|
151
|
+
before,
|
|
152
|
+
after: null
|
|
153
|
+
});
|
|
154
|
+
return Promise.resolve(diffs.toSorted((a, b) => a.path.localeCompare(b.path, "en")));
|
|
155
|
+
}
|
|
156
|
+
mergeBranch(branch, into, opts) {
|
|
157
|
+
const source = this.refs.get(branch);
|
|
158
|
+
if (!source) return Promise.resolve({
|
|
159
|
+
merged: false,
|
|
160
|
+
sha: null,
|
|
161
|
+
pullRequestUrl: null
|
|
162
|
+
});
|
|
163
|
+
this.refs.set(into, new Map(source));
|
|
164
|
+
if (opts?.removeSourceBranch) this.refs.delete(branch);
|
|
165
|
+
return Promise.resolve({
|
|
166
|
+
merged: true,
|
|
167
|
+
sha: this.commits.findLast((c) => c.branch === branch)?.sha ?? null,
|
|
168
|
+
pullRequestUrl: null
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
isMerged(branch, into) {
|
|
172
|
+
const head = this.refs.get(branch);
|
|
173
|
+
if (!head) return Promise.resolve(true);
|
|
174
|
+
const target = this.tree(into ?? CONTENTRAIN_BRANCH);
|
|
175
|
+
for (const [path, content] of head) if (target.get(path) !== content) return Promise.resolve(false);
|
|
176
|
+
return Promise.resolve(true);
|
|
177
|
+
}
|
|
178
|
+
getDefaultBranch() {
|
|
179
|
+
return Promise.resolve("main");
|
|
180
|
+
}
|
|
181
|
+
tree(ref) {
|
|
182
|
+
let tree = this.refs.get(ref);
|
|
183
|
+
if (!tree) {
|
|
184
|
+
tree = /* @__PURE__ */ new Map();
|
|
185
|
+
this.refs.set(ref, tree);
|
|
186
|
+
}
|
|
187
|
+
return tree;
|
|
188
|
+
}
|
|
189
|
+
async readWorkflow(ref) {
|
|
190
|
+
try {
|
|
191
|
+
const raw = await this.readFile(".contentrain/config.json", ref);
|
|
192
|
+
return JSON.parse(raw).workflow === "review" ? "review" : "auto-merge";
|
|
193
|
+
} catch {
|
|
194
|
+
return "auto-merge";
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
/** Repo-relative, forward-slash, no leading `./` — the contract's path shape. */
|
|
199
|
+
function normalize(path) {
|
|
200
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
203
|
+
export { MemoryProvider };
|
|
204
|
+
|
|
205
|
+
//# sourceMappingURL=memory-provider.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-provider.mjs","names":[],"sources":["../../src/testing/memory-provider.ts"],"sourcesContent":["import { CONTENTRAIN_BRANCH } from '@contentrain/types'\nimport type {\n ApplyPlanInput,\n Branch,\n Commit,\n FileChange,\n FileDiff,\n MergeResult,\n ProviderCapabilities,\n RepoProvider,\n WriteReadiness,\n} from '../core/contracts/index.js'\n\n/**\n * MemoryProvider — a complete `RepoProvider` backed by `Map`s.\n *\n * Why this exists: the write-side tool tests were spending their entire\n * wall-clock in `posix_spawn`. A `contentrain_init` costs 33 git subprocesses\n * and a content save 26, and on macOS each one is dominated by fork cost\n * rather than by git. Tests that assert \"does model_save reject an invalid\n * field type\" were paying for worktree creation, merge and ref surgery to\n * find out.\n *\n * None of that is what those tests are about. The provider contract is the\n * seam the engine was already built around — `@contentrain/types` names \"a\n * mock in a test suite\" as a legitimate implementation — so this fills it in\n * and the tool layer runs unchanged, with zero subprocesses.\n *\n * What it is faithful about, because tests depend on it:\n * - branches are real snapshots, forked from `base` at fork time, so a\n * write to a `cr/*` branch is invisible on `contentrain` until it merges\n * - the workflow decision reads the project's own `config.json`, so\n * auto-merge and review are both genuinely exercised\n * - `listDirectory` returns immediate children only, and directories\n * without a trailing entry still report as existing, matching `readdir`\n *\n * What it deliberately does not model: conflicts, worktrees, selective sync\n * into a developer's working tree, and anything else whose behaviour is a\n * property of git rather than of Contentrain. A test that needs those needs a\n * real repository — keep those, and only those, on the git fixture path.\n */\n\nconst MEMORY_CAPABILITIES: ProviderCapabilities = {\n // True: this provider owns the merge decision, exactly as a worktree does.\n // The tool layer branches on this to choose the auto-merge path, which is\n // the path most write tests are actually about.\n localWorktree: true,\n sourceRead: true,\n sourceWrite: true,\n pushRemote: false,\n branchProtection: false,\n pullRequestFallback: false,\n astScan: false,\n}\n\nexport interface MemoryProviderOptions {\n /** Seed files, keyed by repo-relative path. */\n files?: Record<string, string>\n /** Override the capability set — e.g. to exercise the remote-provider path. */\n capabilities?: Partial<ProviderCapabilities>\n /** Refuse writes, as a local repo does under branch pressure. */\n writeReadiness?: WriteReadiness\n}\n\n/** A commit as recorded by the provider, with the changes that produced it. */\nexport interface RecordedCommit extends Commit {\n branch: string\n changes: FileChange[]\n}\n\ntype Tree = Map<string, string>\n\nexport class MemoryProvider implements RepoProvider {\n readonly capabilities: ProviderCapabilities\n\n /** Every commit in order, with its changes — the assertion surface. */\n readonly commits: RecordedCommit[] = []\n\n private readonly refs = new Map<string, Tree>()\n private writeReadiness: WriteReadiness\n private sequence = 0\n\n constructor(options: MemoryProviderOptions = {}) {\n this.capabilities = { ...MEMORY_CAPABILITIES, ...options.capabilities }\n this.writeReadiness = options.writeReadiness ?? { blocked: false }\n this.refs.set(CONTENTRAIN_BRANCH, new Map(Object.entries(options.files ?? {})))\n }\n\n // ─── Inspection (test surface, not part of RepoProvider) ───\n\n /** Whole tree of a ref as a plain object — snapshot-friendly. */\n snapshot(ref: string = CONTENTRAIN_BRANCH): Record<string, string> {\n return Object.fromEntries([...(this.refs.get(ref) ?? new Map())].toSorted(\n ([a], [b]) => a.localeCompare(b, 'en'),\n ))\n }\n\n /** Every path on a ref, sorted. */\n paths(ref: string = CONTENTRAIN_BRANCH): string[] {\n return [...(this.refs.get(ref) ?? new Map()).keys()].toSorted((a, b) => a.localeCompare(b, 'en'))\n }\n\n /** Seed or overwrite a file without going through a commit. */\n seed(path: string, content: string, ref: string = CONTENTRAIN_BRANCH): void {\n this.tree(ref).set(path, content)\n }\n\n /** Start refusing (or accepting) writes, as branch pressure would. */\n setWriteReadiness(readiness: WriteReadiness): void {\n this.writeReadiness = readiness\n }\n\n // ─── RepoReader ───\n\n readFile(path: string, ref?: string): Promise<string> {\n const content = this.tree(ref ?? CONTENTRAIN_BRANCH).get(normalize(path))\n if (content === undefined) {\n return Promise.reject(new Error(`ENOENT: no such file, open '${path}'`))\n }\n return Promise.resolve(content)\n }\n\n listDirectory(path: string, ref?: string): Promise<string[]> {\n const prefix = normalize(path) === '' ? '' : `${normalize(path)}/`\n const names = new Set<string>()\n for (const key of this.tree(ref ?? CONTENTRAIN_BRANCH).keys()) {\n if (!key.startsWith(prefix)) continue\n const rest = key.slice(prefix.length)\n if (rest === '') continue\n // Immediate child only: a nested path contributes its directory name.\n names.add(rest.split('/')[0]!)\n }\n return Promise.resolve([...names].toSorted((a, b) => a.localeCompare(b, 'en')))\n }\n\n fileExists(path: string, ref?: string): Promise<boolean> {\n const target = normalize(path)\n const tree = this.tree(ref ?? CONTENTRAIN_BRANCH)\n if (tree.has(target)) return Promise.resolve(true)\n // Directories exist implicitly, the way they do on a filesystem.\n const prefix = `${target}/`\n for (const key of tree.keys()) {\n if (key.startsWith(prefix)) return Promise.resolve(true)\n }\n return Promise.resolve(false)\n }\n\n // ─── RepoWriter ───\n\n checkWriteReadiness(): Promise<WriteReadiness> {\n return Promise.resolve(this.writeReadiness)\n }\n\n async applyPlan(input: ApplyPlanInput): Promise<Commit> {\n const base = input.base ?? CONTENTRAIN_BRANCH\n // Fork at write time, like a real branch: the feature branch sees base as\n // it is now, and later base commits do not leak into it.\n const tree = new Map(this.refs.get(input.branch) ?? this.tree(base))\n for (const change of input.changes) {\n if (change.content === null) tree.delete(normalize(change.path))\n else tree.set(normalize(change.path), change.content)\n }\n this.refs.set(input.branch, tree)\n\n const workflow = input.workflowOverride ?? await this.readWorkflow(base)\n const autoMerge = this.capabilities.localWorktree && workflow === 'auto-merge'\n if (autoMerge) this.refs.set(base, new Map(tree))\n\n const commit: RecordedCommit = {\n sha: `mem${(++this.sequence).toString().padStart(9, '0')}`,\n message: input.message,\n author: input.author,\n // Fixed, not `new Date()`: a timestamp that moves makes every snapshot\n // assertion a false failure.\n timestamp: new Date(Date.UTC(2026, 0, 1, 0, 0, this.sequence)).toISOString(),\n branch: input.branch,\n changes: input.changes,\n ...(this.capabilities.localWorktree\n ? { workflowAction: autoMerge ? 'auto-merged' as const : 'pending-review' as const }\n : {}),\n }\n this.commits.push(commit)\n return commit\n }\n\n // ─── Branches ───\n\n listBranches(prefix?: string): Promise<Branch[]> {\n const names = [...this.refs.keys()].filter(n => !prefix || n.startsWith(prefix))\n return Promise.resolve(names.toSorted((a, b) => a.localeCompare(b, 'en')).map(name => ({\n name,\n sha: this.commits.findLast(c => c.branch === name)?.sha ?? 'mem000000000',\n })))\n }\n\n createBranch(name: string, fromRef?: string): Promise<void> {\n this.refs.set(name, new Map(this.tree(fromRef ?? CONTENTRAIN_BRANCH)))\n return Promise.resolve()\n }\n\n deleteBranch(name: string): Promise<void> {\n this.refs.delete(name)\n return Promise.resolve()\n }\n\n getBranchDiff(branch: string, base?: string): Promise<FileDiff[]> {\n const head = this.tree(branch)\n const root = this.tree(base ?? CONTENTRAIN_BRANCH)\n const diffs: FileDiff[] = []\n for (const [path, after] of head) {\n const before = root.get(path) ?? null\n if (before === after) continue\n diffs.push({ path, status: before === null ? 'added' : 'modified', before, after })\n }\n for (const [path, before] of root) {\n if (!head.has(path)) diffs.push({ path, status: 'removed', before, after: null })\n }\n return Promise.resolve(diffs.toSorted((a, b) => a.path.localeCompare(b.path, 'en')))\n }\n\n mergeBranch(branch: string, into: string, opts?: { removeSourceBranch?: boolean }): Promise<MergeResult> {\n const source = this.refs.get(branch)\n if (!source) return Promise.resolve({ merged: false, sha: null, pullRequestUrl: null })\n this.refs.set(into, new Map(source))\n if (opts?.removeSourceBranch) this.refs.delete(branch)\n return Promise.resolve({\n merged: true,\n sha: this.commits.findLast(c => c.branch === branch)?.sha ?? null,\n pullRequestUrl: null,\n })\n }\n\n isMerged(branch: string, into?: string): Promise<boolean> {\n const head = this.refs.get(branch)\n if (!head) return Promise.resolve(true)\n const target = this.tree(into ?? CONTENTRAIN_BRANCH)\n for (const [path, content] of head) {\n if (target.get(path) !== content) return Promise.resolve(false)\n }\n return Promise.resolve(true)\n }\n\n getDefaultBranch(): Promise<string> {\n return Promise.resolve('main')\n }\n\n // ─── Internals ───\n\n private tree(ref: string): Tree {\n let tree = this.refs.get(ref)\n if (!tree) {\n tree = new Map()\n this.refs.set(ref, tree)\n }\n return tree\n }\n\n private async readWorkflow(ref: string): Promise<'auto-merge' | 'review'> {\n try {\n const raw = await this.readFile('.contentrain/config.json', ref)\n const workflow = (JSON.parse(raw) as { workflow?: unknown }).workflow\n return workflow === 'review' ? 'review' : 'auto-merge'\n } catch {\n return 'auto-merge'\n }\n }\n}\n\n/** Repo-relative, forward-slash, no leading `./` — the contract's path shape. */\nfunction normalize(path: string): string {\n return path.replace(/\\\\/g, '/').replace(/^\\.\\//, '').replace(/^\\/+/, '').replace(/\\/+$/, '')\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"annotations.d.mts","names":[],"sources":["../../src/tools/annotations.ts"],"mappings":";;;;;AAcA;;;;;
|
|
1
|
+
{"version":3,"file":"annotations.d.mts","names":[],"sources":["../../src/tools/annotations.ts"],"mappings":";;;;;AAcA;;;;;AAiNA;;;;;cAjNa,gBAAA,EAAkB,MAAA,SAAe,eAAA;;;;;;;;cAiNjC,UAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as TOOL_NAMES, t as TOOL_ANNOTATIONS } from "../annotations-
|
|
1
|
+
import { n as TOOL_NAMES, t as TOOL_ANNOTATIONS } from "../annotations-C-tQVmot.mjs";
|
|
2
2
|
export { TOOL_ANNOTATIONS, TOOL_NAMES };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as isToolAvailable, t as TOOL_REQUIREMENTS } from "../availability-
|
|
1
|
+
import { n as isToolAvailable, t as TOOL_REQUIREMENTS } from "../availability-Dgcmvv99.mjs";
|
|
2
2
|
export { TOOL_REQUIREMENTS, isToolAvailable };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { a as deleteRemoteBranch
|
|
4
|
-
import {
|
|
1
|
+
import { n as authorConfig, r as createGit } from "./identity-D8QoGxhT.mjs";
|
|
2
|
+
import { t as readConfig } from "./config-VlRKRwAS.mjs";
|
|
3
|
+
import { a as deleteRemoteBranch } from "./branch-lifecycle-B6PXZ2N7.mjs";
|
|
4
|
+
import { r as writeContext } from "./context-C3x6I3cE.mjs";
|
|
5
|
+
import { t as branchTimestamp } from "./id-BjhaIxTL.mjs";
|
|
5
6
|
import { join } from "node:path";
|
|
6
7
|
import { CONTENTRAIN_BRANCH } from "@contentrain/types";
|
|
7
8
|
import { rm } from "node:fs/promises";
|
|
8
|
-
import
|
|
9
|
+
import "simple-git";
|
|
9
10
|
import { tmpdir } from "node:os";
|
|
10
11
|
import { randomUUID } from "node:crypto";
|
|
11
12
|
//#region src/providers/local/migration.ts
|
|
@@ -64,7 +65,7 @@ async function migrateLegacyBranches(git, baseBranch) {
|
|
|
64
65
|
//#endregion
|
|
65
66
|
//#region src/git/transaction.ts
|
|
66
67
|
async function ensureContentBranch(projectRoot) {
|
|
67
|
-
const git =
|
|
68
|
+
const git = createGit(projectRoot);
|
|
68
69
|
const config = await readConfig(projectRoot);
|
|
69
70
|
if ((await git.branchLocal()).all.includes(CONTENTRAIN_BRANCH)) return;
|
|
70
71
|
const baseBranch = config?.repository?.default_branch || (await git.raw(["branch", "--show-current"])).trim() || "main";
|
|
@@ -80,7 +81,7 @@ async function ensureContentBranch(projectRoot) {
|
|
|
80
81
|
} catch {}
|
|
81
82
|
}
|
|
82
83
|
async function selectiveSync(projectRoot, _worktreePath, contentrainTip, _previousBaseRef, dirtyFilesBeforeUpdate) {
|
|
83
|
-
const git =
|
|
84
|
+
const git = createGit(projectRoot);
|
|
84
85
|
const synced = [];
|
|
85
86
|
const skipped = [];
|
|
86
87
|
const compareRef = _previousBaseRef ?? contentrainTip;
|
|
@@ -178,7 +179,7 @@ async function selectiveSync(projectRoot, _worktreePath, contentrainTip, _previo
|
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
async function createTransaction(projectRoot, branchName, options) {
|
|
181
|
-
const git =
|
|
182
|
+
const git = createGit(projectRoot);
|
|
182
183
|
const config = await readConfig(projectRoot);
|
|
183
184
|
const workflow = options?.workflowOverride ?? config?.workflow ?? "auto-merge";
|
|
184
185
|
const remoteName = process.env["CONTENTRAIN_REMOTE"] ?? "origin";
|
|
@@ -204,7 +205,7 @@ async function createTransaction(projectRoot, branchName, options) {
|
|
|
204
205
|
worktreePath,
|
|
205
206
|
CONTENTRAIN_BRANCH
|
|
206
207
|
]);
|
|
207
|
-
const wtGit =
|
|
208
|
+
const wtGit = createGit(worktreePath, { config: authorConfig() });
|
|
208
209
|
try {
|
|
209
210
|
await wtGit.merge([baseBranch, "--no-edit"]);
|
|
210
211
|
} catch {
|
|
@@ -335,7 +336,7 @@ async function createTransaction(projectRoot, branchName, options) {
|
|
|
335
336
|
};
|
|
336
337
|
}
|
|
337
338
|
async function mergeBranch(projectRoot, branchName) {
|
|
338
|
-
const git =
|
|
339
|
+
const git = createGit(projectRoot);
|
|
339
340
|
const config = await readConfig(projectRoot);
|
|
340
341
|
const remoteName = process.env["CONTENTRAIN_REMOTE"] ?? "origin";
|
|
341
342
|
const baseBranch = process.env["CONTENTRAIN_BRANCH"] ?? config?.repository?.default_branch ?? ((await git.raw(["branch", "--show-current"])).trim() || "main");
|
|
@@ -353,7 +354,7 @@ async function mergeBranch(projectRoot, branchName) {
|
|
|
353
354
|
worktreePath,
|
|
354
355
|
CONTENTRAIN_BRANCH
|
|
355
356
|
]);
|
|
356
|
-
const wtGit =
|
|
357
|
+
const wtGit = createGit(worktreePath, { config: authorConfig() });
|
|
357
358
|
try {
|
|
358
359
|
try {
|
|
359
360
|
await wtGit.merge([branchName, "--no-edit"]);
|
|
@@ -491,4 +492,4 @@ async function regenerateContextOnContentrain(wtGit, worktreePath, contextUpdate
|
|
|
491
492
|
//#endregion
|
|
492
493
|
export { mergeBranch as i, createTransaction as n, ensureContentBranch as r, buildBranchName as t };
|
|
493
494
|
|
|
494
|
-
//# sourceMappingURL=transaction-
|
|
495
|
+
//# sourceMappingURL=transaction-BW9ektWu.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-BW9ektWu.mjs","names":["removeFile"],"sources":["../src/providers/local/migration.ts","../src/git/transaction.ts"],"sourcesContent":["import type { SimpleGit } from 'simple-git'\n\n/**\n * Migration: the first MCP release used `contentrain/*` feature branches.\n * Once we introduced the singleton `contentrain` branch (tracking the\n * committed content state), those old feature branches became a ref-\n * namespace conflict — git cannot hold both `contentrain` (a leaf ref)\n * and `contentrain/foo` (implying a directory) simultaneously.\n *\n * `migrateLegacyBranches` removes the old-prefix branches so the\n * singleton `contentrain` ref can be created. It is idempotent and\n * safe to call before every `ensureContentBranch` run.\n *\n * Strategy:\n * 1. Delete merged `contentrain/*` branches first (`-d`). Their commits\n * are already on the base branch via the old auto-merge flow.\n * 2. Force-delete whatever remains (`-D`). Any unmerged leftover is\n * from an abandoned or partially-committed legacy branch — content\n * on `main` always wins, and the singleton `contentrain` branch is\n * about to be created from `main`/`baseBranch` anyway.\n *\n * Returns the number of branches that were deleted. Callers may log it;\n * the git transaction layer does not need the count for correctness.\n */\nexport async function migrateLegacyBranches(\n git: SimpleGit,\n baseBranch: string,\n): Promise<number> {\n const branches = await git.branchLocal()\n const oldPrefixBranches = branches.all.filter(b => b.startsWith('contentrain/'))\n if (oldPrefixBranches.length === 0) return 0\n\n let deleted = 0\n\n // 1) Delete merged legacy branches first — the safe path.\n let mergedLegacy: string[] = []\n try {\n const mergedOutput = await git.raw(['branch', '--merged', baseBranch])\n mergedLegacy = mergedOutput.split('\\n')\n .map(b => b.trim().replace(/^\\*\\s*/, ''))\n .filter(b => b.startsWith('contentrain/'))\n } catch {\n // `branch --merged` fails before baseBranch exists — fall through.\n }\n\n for (const b of mergedLegacy) {\n try {\n await git.raw(['branch', '-d', b])\n deleted++\n } catch {\n // Branch may be protected or already gone — safe to skip.\n }\n }\n\n // 2) Force-delete any unmerged legacy branches still present.\n const remaining = (await git.branchLocal()).all.filter(b => b.startsWith('contentrain/'))\n for (const b of remaining) {\n try {\n await git.raw(['branch', '-D', b])\n deleted++\n } catch {\n // Skip — best-effort cleanup.\n }\n }\n\n return deleted\n}\n","import { type SimpleGit } from 'simple-git'\nimport { createGit } from '../git/identity.js'\nimport { join } from 'node:path'\nimport { rm as removeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { randomUUID } from 'node:crypto'\nimport { readConfig } from '../core/config.js'\nimport { writeContext } from '../core/context.js'\nimport { deleteRemoteBranch, type RemoteDeleteResult } from './branch-lifecycle.js'\nimport { authorConfig } from './identity.js'\nimport { branchTimestamp } from '../util/id.js'\nimport { migrateLegacyBranches } from '../providers/local/migration.js'\nimport type { SyncResult, WorkflowMode } from '@contentrain/types'\nimport { CONTENTRAIN_BRANCH } from '@contentrain/types'\n\nexport interface ContextUpdate {\n tool: string\n model: string\n locale?: string\n entries?: string[]\n}\n\nexport interface GitTransaction {\n worktree: string\n branch: string\n write(callback: (worktreePath: string) => Promise<void>): Promise<void>\n commit(message: string, contextUpdate?: ContextUpdate): Promise<string>\n complete(): Promise<{ action: 'auto-merged' | 'pending-review'; commit: string; sync?: SyncResult; warning?: string }>\n cleanup(): Promise<void>\n}\n\nexport async function ensureContentBranch(projectRoot: string): Promise<void> {\n const git = createGit(projectRoot)\n const config = await readConfig(projectRoot)\n\n // Check if contentrain branch exists locally\n const branches = await git.branchLocal()\n if (branches.all.includes(CONTENTRAIN_BRANCH)) return\n\n // Detect base branch\n const baseBranch = config?.repository?.default_branch\n || (await git.raw(['branch', '--show-current'])).trim()\n || 'main'\n\n // Clean up legacy `contentrain/*` feature branches so the singleton\n // `contentrain` ref can be created. Idempotent — safe to call even\n // when no legacy branches exist.\n await migrateLegacyBranches(git, baseBranch)\n\n // Create contentrain branch from base\n await git.branch([CONTENTRAIN_BRANCH, baseBranch])\n\n // Push to remote if exists\n const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'\n try {\n const remotes = await git.getRemotes()\n if (remotes.some(r => r.name === remoteName)) {\n await git.push(['-u', remoteName, CONTENTRAIN_BRANCH])\n }\n } catch {\n // Remote push is best-effort\n }\n}\n\nasync function selectiveSync(\n projectRoot: string,\n _worktreePath: string,\n contentrainTip: string,\n _previousBaseRef?: string,\n dirtyFilesBeforeUpdate?: Set<string>,\n): Promise<SyncResult> {\n const git = createGit(projectRoot)\n const synced: string[] = []\n const skipped: string[] = []\n\n // Use git plumbing to find ALL files that differ between old and new commits.\n // diff-tree is fast and ignores working tree / index state entirely.\n // Not limited to .contentrain/ — some ops also modify .gitignore, etc.\n const compareRef = _previousBaseRef ?? contentrainTip\n let changedFiles: string[] = []\n try {\n const diffOutput = await git.raw([\n 'diff-tree', '--name-only', '-r', '--no-commit-id',\n compareRef, contentrainTip,\n ])\n changedFiles = diffOutput.split('\\n').filter(f => f.trim().length > 0)\n } catch {\n // Fallback: list .contentrain/ files from the contentrainTip commit\n try {\n const lsOutput = await git.raw(['ls-tree', '-r', '--name-only', contentrainTip, '.contentrain/'])\n changedFiles = lsOutput.split('\\n').filter(f => f.trim().length > 0)\n } catch {\n return { synced, skipped }\n }\n }\n\n if (changedFiles.length === 0) return { synced, skipped }\n\n // Use pre-captured dirty files (before update-ref) to avoid false positives.\n // After update-ref, files appear as \"modified\" in status even though the developer\n // didn't touch them. We use the pre-update state to know what was truly dirty.\n const dirtyFiles = dirtyFilesBeforeUpdate ?? new Set<string>()\n\n // Which changed files still exist in contentrainTip (HEAD after update-ref)?\n // ONE `ls-tree` over the changed paths lists exactly the survivors, instead\n // of a `cat-file -e` spawn per file. Falls back to per-file probing if\n // ls-tree fails so behavior is preserved on any edge.\n const filesInTip = new Set<string>()\n try {\n const lsOutput = await git.raw(['ls-tree', '-r', '--name-only', contentrainTip, '--', ...changedFiles])\n for (const f of lsOutput.split('\\n')) {\n const trimmed = f.trim()\n if (trimmed) filesInTip.add(trimmed)\n }\n } catch {\n for (const file of changedFiles) {\n try {\n await git.raw(['cat-file', '-e', `${contentrainTip}:${file}`])\n filesInTip.add(file)\n } catch {\n // File does not exist in tip (was deleted)\n }\n }\n }\n\n // Partition: dirty developer files are skipped; survivors get checked out\n // from HEAD; the rest were deleted in the new HEAD and are removed on disk.\n const toCheckout: string[] = []\n const toRemove: string[] = []\n for (const file of changedFiles) {\n if (dirtyFiles.has(file)) skipped.push(file)\n else if (filesInTip.has(file)) toCheckout.push(file)\n else toRemove.push(file)\n }\n\n // ONE `git checkout HEAD -- f1 f2 …` restores every clean survivor at once.\n // On failure, fall back to per-file so a single unresolvable path still\n // yields precise skip accounting (dirty files were already excluded).\n if (toCheckout.length > 0) {\n try {\n await git.checkout(['HEAD', '--', ...toCheckout])\n synced.push(...toCheckout)\n } catch {\n for (const file of toCheckout) {\n try {\n await git.checkout(['HEAD', '--', file])\n synced.push(file)\n } catch {\n skipped.push(file)\n }\n }\n }\n }\n\n // Deletions are working-tree fs removals — no git spawn, safe to parallelize.\n await Promise.all(toRemove.map(async (file) => {\n try {\n await removeFile(join(projectRoot, file), { force: true })\n synced.push(file)\n } catch {\n skipped.push(file)\n }\n }))\n\n const warning = skipped.length > 0\n ? `${skipped.length} file(s) skipped due to local changes: ${skipped.join(', ')}. Commit your changes, then run: git checkout HEAD -- ${skipped.join(' ')}`\n : undefined\n\n return { synced, skipped, warning }\n}\n\nexport async function createTransaction(\n projectRoot: string,\n branchName: string,\n options?: { workflowOverride?: WorkflowMode },\n): Promise<GitTransaction> {\n const git = createGit(projectRoot)\n const config = await readConfig(projectRoot)\n const workflow = options?.workflowOverride ?? config?.workflow ?? 'auto-merge'\n\n const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'\n\n // Detect base branch + current branch + remote in ONE batch\n // (reduces subprocess spawns from 4 to 2)\n let baseBranch = process.env['CONTENTRAIN_BRANCH'] ?? config?.repository?.default_branch ?? ''\n let currentBranch = ''\n let hasRemote = false\n\n const [branchResult, remotes] = await Promise.all([\n git.raw(['branch', '--show-current']).catch(() => ''),\n git.getRemotes().catch(() => []),\n ])\n currentBranch = branchResult.trim()\n if (!baseBranch) baseBranch = currentBranch || 'main'\n hasRemote = (remotes as { name: string }[]).some(r => r.name === remoteName)\n\n // Check if developer is on contentrain branch\n if (currentBranch === CONTENTRAIN_BRANCH) {\n throw Object.assign(new Error(\n `The '${CONTENTRAIN_BRANCH}' branch is checked out in your working directory. `\n + `Contentrain manages this branch automatically. `\n + `Switch to your working branch and retry.`,\n ), {\n code: 'CONTENT_BRANCH_CHECKED_OUT',\n agent_hint: 'Ask the developer to switch to their working branch (e.g., main or a feature branch), then retry the operation.',\n developer_action: `git checkout ${baseBranch}`,\n })\n }\n\n // Ensure contentrain branch exists (with migration for old contentrain/* branches)\n await ensureContentBranch(projectRoot)\n\n // Fetch latest from remote (parallel fetch for both branches)\n if (hasRemote) {\n await Promise.all([\n git.fetch(remoteName, baseBranch).catch(() => {}),\n git.fetch(remoteName, CONTENTRAIN_BRANCH).catch(() => {}),\n ])\n }\n\n const worktreePath = join(tmpdir(), `cr-${randomUUID()}`)\n const branch = branchName\n\n // Create worktree on contentrain branch\n await git.raw(['worktree', 'add', worktreePath, CONTENTRAIN_BRANCH])\n\n // Commit identity comes from `-c user.*` config (see authorConfig) — passed\n // as args, never via `.env()`, so simple-git's block-unsafe guard is never\n // triggered by an inherited EDITOR/GIT_ASKPASS/etc.\n const wtGit = createGit(worktreePath, { config: authorConfig() })\n\n // Sync contentrain with base branch (bring main changes into contentrain)\n try {\n await wtGit.merge([baseBranch, '--no-edit'])\n } catch {\n try { await wtGit.merge(['--abort']) } catch { /* not in merge state */ }\n if (hasRemote) {\n try {\n await wtGit.merge([`${remoteName}/${baseBranch}`, '--no-edit'])\n } catch {\n try { await wtGit.merge(['--abort']) } catch { /* ignore */ }\n }\n }\n }\n\n // Sync with remote contentrain if exists\n if (hasRemote) {\n try {\n await wtGit.merge([`${remoteName}/${CONTENTRAIN_BRANCH}`, '--no-edit'])\n } catch {\n try { await wtGit.merge(['--abort']) } catch { /* ignore */ }\n }\n }\n\n // Create feature branch from contentrain\n await wtGit.checkout(['-b', branch])\n\n let commitHash = ''\n let pendingReview = false\n // Set once the feature branch has been merged into the contentrain branch,\n // which is what makes deleting it in cleanup() safe — the work is reachable\n // from contentrain at that point.\n let mergedIntoContentrain = false\n let savedContextUpdate: ContextUpdate | undefined\n\n return {\n worktree: worktreePath,\n branch,\n\n async write(callback) {\n await callback(worktreePath)\n },\n\n async commit(message, contextUpdate?) {\n // context.json is intentionally NOT committed on the feature branch — it\n // is regenerated on the contentrain branch after the merge (see\n // complete()). Committing it per-branch caused cross-branch merge\n // conflicts on a single mutable file. `--no-verify` keeps the repo's\n // commit-msg / pre-commit hooks (commitlint, lefthook, husky) from\n // rejecting these machine-generated infra commits.\n savedContextUpdate = contextUpdate\n await wtGit.add('.')\n const result = await wtGit.commit(message, { '--allow-empty': null, '--no-verify': null })\n commitHash = result.commit || ''\n return commitHash\n },\n\n async complete() {\n if (workflow === 'review') {\n // Mark the branch as surviving BEFORE attempting the push. The commit\n // already exists at this point, so a push failure (no write access,\n // offline, expired credentials) must not make cleanup() treat this as\n // a failed transaction and delete the branch — that silently discards\n // committed work. Pushing is a convenience here; contentrain_submit is\n // the documented way to publish a review branch.\n pendingReview = true\n\n let warning: string | undefined\n if (hasRemote) {\n try {\n await git.push(remoteName, branch)\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error)\n warning = `Changes are committed locally on \"${branch}\", but pushing to \"${remoteName}\" failed: ${detail.trim()}. `\n + `The branch is intact — run contentrain_submit once you have push access, or merge it locally with contentrain_merge.`\n }\n }\n\n return { action: 'pending-review', commit: commitHash, warning }\n }\n\n // auto-merge: merge feature branch into contentrain, then advance base\n\n // Switch to contentrain branch in worktree\n await wtGit.checkout(CONTENTRAIN_BRANCH)\n\n // Merge feature branch into contentrain\n try {\n await wtGit.merge([branch, '--no-edit'])\n } catch {\n try {\n await wtGit.merge(['--abort'])\n } catch { /* not in merge state */ }\n throw Object.assign(new Error(\n `Merge conflict when merging branch \"${branch}\" into \"${CONTENTRAIN_BRANCH}\". `\n + `The branch still exists with your changes intact. `\n + `Resolve the conflict manually, or delete the branch and retry.`,\n ), {\n code: 'CONTENT_BRANCH_MERGE_CONFLICT',\n agent_hint: 'The feature branch could not be merged into the contentrain branch. Ask the developer to resolve the conflict.',\n developer_action: `git checkout ${CONTENTRAIN_BRANCH} && git merge ${branch}`,\n })\n }\n // The commit is now reachable from contentrain, so cleanup() may prune\n // the feature branch. Anything that fails after this point still leaves\n // the work safe.\n mergedIntoContentrain = true\n\n // Regenerate context.json on the contentrain branch (post-merge,\n // single-threaded) and fold it into the tip before advancing the base.\n if (savedContextUpdate) {\n await regenerateContextOnContentrain(wtGit, worktreePath, savedContextUpdate)\n }\n\n // Get contentrain tip + old base ref + dirty files in parallel\n const [contentrainTip, previousBaseRef, statusBeforeUpdate] = await Promise.all([\n wtGit.raw(['rev-parse', 'HEAD']).then(s => s.trim()),\n git.raw(['rev-parse', baseBranch]).then(s => s.trim()),\n git.status(),\n ])\n const dirtyFilesBeforeUpdate = new Set(statusBeforeUpdate.files.map(f => f.path))\n\n // Verify fast-forward: baseBranch must be an ancestor of contentrainTip\n // (guaranteed by the merge above, but verify for safety).\n // `rev-list --count` instead of `merge-base --is-ancestor`: the latter\n // signals via exit code with empty stderr, which simple-git reports as\n // success — the guard would silently pass on divergence.\n if (!(await isAncestor(git, previousBaseRef, contentrainTip))) {\n throw Object.assign(new Error(\n `Cannot fast-forward \"${baseBranch}\" to contentrain tip. `\n + `The base branch has diverged. Merge \"${baseBranch}\" into \"${CONTENTRAIN_BRANCH}\" first.`,\n ), {\n code: 'BASE_UPDATE_FAILED',\n agent_hint: `The base branch has commits not in contentrain. Merge ${baseBranch} into ${CONTENTRAIN_BRANCH} first.`,\n developer_action: `git checkout ${CONTENTRAIN_BRANCH} && git merge ${baseBranch} && git checkout ${baseBranch}`,\n })\n }\n\n // Advance base branch to contentrain tip via update-ref\n await git.raw(['update-ref', `refs/heads/${baseBranch}`, contentrainTip])\n\n // Refresh index to match new HEAD.\n // update-ref moves the branch pointer but leaves the index stale.\n // read-tree updates the index to match HEAD without touching the working tree.\n try {\n await git.raw(['read-tree', 'HEAD'])\n } catch {\n // fallback: try reset for older git versions\n try { await git.raw(['reset', 'HEAD']) } catch { /* ignore */ }\n }\n\n // Selective sync: copy .contentrain/ files to developer's working tree\n const sync = await selectiveSync(projectRoot, worktreePath, contentrainTip, previousBaseRef, dirtyFilesBeforeUpdate)\n\n // Push to remote (best-effort with retry)\n if (hasRemote) {\n // Push contentrain branch\n try {\n await git.push(remoteName, CONTENTRAIN_BRANCH)\n } catch {\n // Retry: fetch, merge, push\n try {\n await wtGit.fetch(remoteName, CONTENTRAIN_BRANCH)\n await wtGit.merge([`${remoteName}/${CONTENTRAIN_BRANCH}`, '--no-edit'])\n await git.push(remoteName, CONTENTRAIN_BRANCH)\n } catch {\n // Push failed after retry — continue, local state is fine\n }\n }\n\n // Push base branch\n try {\n await git.push(remoteName, baseBranch)\n } catch {\n // push may fail, local merge succeeded\n }\n }\n\n return {\n action: 'auto-merged' as const,\n commit: commitHash,\n sync,\n ...(sync.warning ? { warning: sync.warning } : {}),\n }\n },\n\n async cleanup() {\n try {\n await git.raw(['worktree', 'remove', worktreePath, '--force'])\n } catch {\n // worktree may already be cleaned up\n }\n // Prune the feature branch only when doing so cannot lose work:\n // - pendingReview: must survive for a later contentrain_submit/merge\n // - commitHash === '': nothing was ever committed, safe to drop\n // - mergedIntoContentrain: the work is reachable from contentrain\n // Anything else means the branch holds a commit that is reachable from\n // nowhere else, and deleting it would silently discard the user's work\n // while the caller reports success. Keeping it may leave a stray cr/*\n // ref after a failure — that is recoverable; a deleted commit is not.\n const holdsUnreachableWork = commitHash !== '' && !mergedIntoContentrain\n if (!pendingReview && !holdsUnreachableWork) {\n await safeDeleteBranch(git, branch)\n }\n },\n }\n}\n\nexport async function mergeBranch(\n projectRoot: string,\n branchName: string,\n): Promise<{ action: 'merged'; commit: string; sync: SyncResult; remote?: RemoteDeleteResult }> {\n const git = createGit(projectRoot)\n const config = await readConfig(projectRoot)\n const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'\n\n // Detect base branch\n const baseBranch = process.env['CONTENTRAIN_BRANCH']\n ?? config?.repository?.default_branch\n ?? ((await git.raw(['branch', '--show-current'])).trim() || 'main')\n\n // Ensure contentrain branch exists\n await ensureContentBranch(projectRoot)\n\n // Check remote\n let hasRemote = false\n try {\n const remotes = await git.getRemotes()\n hasRemote = remotes.some(r => r.name === remoteName)\n } catch {\n hasRemote = false\n }\n\n // Create temp worktree on contentrain branch\n const worktreePath = join(tmpdir(), `cr-merge-${randomUUID()}`)\n await git.raw(['worktree', 'add', worktreePath, CONTENTRAIN_BRANCH])\n\n // Commit identity from `-c user.*` config (see authorConfig) — guard-safe,\n // no `.env()` spread.\n const wtGit = createGit(worktreePath, { config: authorConfig() })\n\n try {\n // Merge the feature branch into contentrain\n try {\n await wtGit.merge([branchName, '--no-edit'])\n } catch {\n try { await wtGit.merge(['--abort']) } catch { /* not in merge state */ }\n throw Object.assign(new Error(\n `Merge conflict when merging branch \"${branchName}\" into \"${CONTENTRAIN_BRANCH}\". `\n + `The branch still exists with your changes intact. `\n + `Resolve the conflict manually, or delete the branch and retry.`,\n ), {\n code: 'CONTENT_BRANCH_MERGE_CONFLICT',\n agent_hint: 'The feature branch could not be merged into the contentrain branch. Ask the developer to resolve the conflict.',\n developer_action: `git checkout ${CONTENTRAIN_BRANCH} && git merge ${branchName}`,\n })\n }\n\n // Regenerate context.json on contentrain post-merge (deterministic,\n // single-threaded) so review-mode branches — which carry no context.json —\n // still produce up-to-date stats once landed.\n await regenerateContextOnContentrain(wtGit, worktreePath, { tool: 'contentrain_merge', model: '*' })\n\n // Get contentrain tip + old base ref + dirty files in parallel\n const [contentrainTip, previousBaseRef, statusBeforeUpdate] = await Promise.all([\n wtGit.raw(['rev-parse', 'HEAD']).then(s => s.trim()),\n git.raw(['rev-parse', baseBranch]).then(s => s.trim()),\n git.status(),\n ])\n const dirtyFilesBeforeUpdate = new Set(statusBeforeUpdate.files.map(f => f.path))\n\n // Verify fast-forward: baseBranch must be an ancestor of contentrainTip.\n // (See complete() — merge-base --is-ancestor is unusable via simple-git.)\n if (!(await isAncestor(git, previousBaseRef, contentrainTip))) {\n throw Object.assign(new Error(\n `Cannot fast-forward \"${baseBranch}\" to contentrain tip. `\n + `The base branch has diverged. Merge \"${baseBranch}\" into \"${CONTENTRAIN_BRANCH}\" first.`,\n ), {\n code: 'BASE_UPDATE_FAILED',\n agent_hint: `The base branch has commits not in contentrain. Merge ${baseBranch} into ${CONTENTRAIN_BRANCH} first.`,\n developer_action: `git checkout ${CONTENTRAIN_BRANCH} && git merge ${baseBranch} && git checkout ${baseBranch}`,\n })\n }\n\n // Advance base branch to contentrain tip via update-ref\n await git.raw(['update-ref', `refs/heads/${baseBranch}`, contentrainTip])\n\n // Refresh index to match new HEAD\n try {\n await git.raw(['read-tree', 'HEAD'])\n } catch {\n try { await git.raw(['reset', 'HEAD']) } catch { /* ignore */ }\n }\n\n // Selective sync: copy .contentrain/ files to developer's working tree\n const sync = await selectiveSync(projectRoot, worktreePath, contentrainTip, previousBaseRef, dirtyFilesBeforeUpdate)\n\n // Push to remote (best-effort)\n if (hasRemote) {\n try {\n await git.push(remoteName, CONTENTRAIN_BRANCH)\n } catch {\n try {\n await wtGit.fetch(remoteName, CONTENTRAIN_BRANCH)\n await wtGit.merge([`${remoteName}/${CONTENTRAIN_BRANCH}`, '--no-edit'])\n await git.push(remoteName, CONTENTRAIN_BRANCH)\n } catch {\n // Push failed after retry — continue, local state is fine\n }\n }\n\n try {\n await git.push(remoteName, baseBranch)\n } catch {\n // push may fail, local merge succeeded\n }\n }\n\n // Prune the now-merged feature branch so merged cr/* refs don't accumulate.\n await safeDeleteBranch(git, branchName)\n\n // Delete the remote copy too (review-mode branches were pushed on save).\n // Best-effort and config-gated inside the helper: a failure surfaces as\n // `remote.warning`, never as a failed merge.\n let remote: RemoteDeleteResult | undefined\n if (hasRemote) {\n remote = await deleteRemoteBranch(projectRoot, branchName, { config })\n }\n\n return {\n action: 'merged' as const,\n commit: contentrainTip,\n sync,\n ...(remote ? { remote } : {}),\n }\n } finally {\n // Cleanup worktree\n try {\n await git.raw(['worktree', 'remove', worktreePath, '--force'])\n } catch {\n // worktree may already be cleaned up\n }\n }\n}\n\nexport function buildBranchName(scope: string, target: string, locale?: string): string {\n const ts = branchTimestamp()\n const parts = ['cr', scope, target]\n if (locale) parts.push(locale)\n parts.push(ts)\n return parts.join('/')\n}\n\n/**\n * True when `ancestor` is an ancestor of (or equal to) `descendant`.\n * Implemented with `rev-list --count` because `merge-base --is-ancestor`\n * signals via exit code with empty stderr — simple-git reports that as\n * success, so it cannot express a negative verdict.\n */\nasync function isAncestor(git: SimpleGit, ancestor: string, descendant: string): Promise<boolean> {\n try {\n const count = Number((await git.raw(['rev-list', '--count', ancestor, `^${descendant}`])).trim())\n return count === 0\n } catch {\n return false\n }\n}\n\n/**\n * Force-delete a local branch, swallowing all errors. Never deletes the\n * singleton `contentrain` branch. Used to prune feature branches after they\n * are merged (auto-merge / contentrain_merge) or when a transaction fails\n * before completing — so failed/merged `cr/*` refs do not accumulate.\n */\nasync function safeDeleteBranch(git: SimpleGit, branch: string): Promise<void> {\n if (!branch || branch === CONTENTRAIN_BRANCH) return\n try {\n await git.raw(['branch', '-D', branch])\n } catch {\n // Branch may not exist, be checked out, or already be deleted — ignore.\n }\n}\n\n/**\n * Regenerate `.contentrain/context.json` deterministically inside a worktree\n * that is currently on the `contentrain` branch, then commit it (hooks\n * bypassed). Called AFTER a feature branch is merged so context.json is only\n * ever written on `contentrain`, single-threaded — eliminating the per-branch\n * merge conflicts that came from committing it on every feature branch.\n */\nasync function regenerateContextOnContentrain(\n wtGit: SimpleGit,\n worktreePath: string,\n contextUpdate: ContextUpdate,\n): Promise<void> {\n await writeContext(worktreePath, contextUpdate)\n await wtGit.add('.contentrain/context.json')\n try {\n await wtGit.commit('[contentrain] context: update', { '--no-verify': null })\n } catch {\n // Nothing staged (context.json unchanged) — fine.\n }\n}\n"],"mappings":"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"}
|