@contentrain/mcp 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -25
- package/dist/annotations-Clhbw2lh.mjs +126 -0
- package/dist/annotations-Clhbw2lh.mjs.map +1 -0
- package/dist/{apply-manager-BkPMfpsN.mjs → apply-manager-D5ym5Jba.mjs} +7 -7
- package/dist/{apply-manager-BkPMfpsN.mjs.map → apply-manager-D5ym5Jba.mjs.map} +1 -1
- package/dist/{astro-parser-BJ-54JGe.mjs → astro-parser-B-nLl0zE.mjs} +3 -3
- package/dist/{astro-parser-BJ-54JGe.mjs.map → astro-parser-B-nLl0zE.mjs.map} +1 -1
- package/dist/{branch-lifecycle-wGJg25LT.mjs → branch-lifecycle-CFVpTpNT.mjs} +33 -3
- package/dist/{branch-lifecycle-wGJg25LT.mjs.map → branch-lifecycle-CFVpTpNT.mjs.map} +1 -1
- package/dist/{compiler-rIC4ETHv.mjs → compiler-k4yttlzM.mjs} +2 -2
- package/dist/{compiler-rIC4ETHv.mjs.map → compiler-k4yttlzM.mjs.map} +1 -1
- package/dist/{compiler-sfc.cjs-BbD04rn_.mjs → compiler-sfc.cjs-D0d_OicF.mjs} +2 -2
- package/dist/{compiler-sfc.cjs-BbD04rn_.mjs.map → compiler-sfc.cjs-D0d_OicF.mjs.map} +1 -1
- package/dist/config-BIl5Ybj0.mjs +40 -0
- package/dist/config-BIl5Ybj0.mjs.map +1 -0
- package/dist/content-manager-DqDLSNQ-.d.mts +63 -0
- package/dist/content-manager-DqDLSNQ-.d.mts.map +1 -0
- package/dist/context-J0OU9Nfp.mjs +102 -0
- package/dist/context-J0OU9Nfp.mjs.map +1 -0
- package/dist/contracts-DfL0BfrD.mjs +2 -0
- package/dist/core/apply-manager.mjs +10 -10
- package/dist/core/config.d.mts +13 -0
- package/dist/core/config.d.mts.map +1 -1
- package/dist/core/config.mjs +2 -3
- package/dist/core/content-manager.d.mts +2 -45
- package/dist/core/content-manager.mjs +3 -4
- package/dist/core/context.d.mts +32 -2
- package/dist/core/context.d.mts.map +1 -1
- package/dist/core/context.mjs +7 -7
- package/dist/core/contracts/index.d.mts +2 -0
- package/dist/core/contracts/index.mjs +2 -0
- package/dist/core/doctor.d.mts +76 -0
- package/dist/core/doctor.d.mts.map +1 -0
- package/dist/core/doctor.mjs +8 -0
- package/dist/core/graph-builder.mjs +3 -4
- package/dist/core/meta-manager.mjs +2 -3
- package/dist/core/model-manager.d.mts +34 -1
- package/dist/core/model-manager.d.mts.map +1 -1
- package/dist/core/model-manager.mjs +3 -4
- package/dist/core/ops/index.d.mts +137 -0
- package/dist/core/ops/index.d.mts.map +1 -0
- package/dist/core/ops/index.mjs +4 -0
- package/dist/core/overlay-reader.d.mts +42 -0
- package/dist/core/overlay-reader.d.mts.map +1 -0
- package/dist/core/overlay-reader.mjs +2 -0
- package/dist/core/scan-config.mjs +2 -3
- package/dist/core/scanner.mjs +4 -5
- package/dist/core/validator/index.d.mts +129 -0
- package/dist/core/validator/index.d.mts.map +1 -0
- package/dist/core/validator/index.mjs +6 -0
- package/dist/{detect-DinOQAmK.mjs → detect-DL9mFmaS.mjs} +2 -2
- package/dist/{detect-DinOQAmK.mjs.map → detect-DL9mFmaS.mjs.map} +1 -1
- package/dist/doctor-CgBoT6h1.mjs +338 -0
- package/dist/doctor-CgBoT6h1.mjs.map +1 -0
- package/dist/{fs-BnIQ4dKj.mjs → fs-DLbVB-Ek.mjs} +2 -2
- package/dist/fs-DLbVB-Ek.mjs.map +1 -0
- package/dist/git/branch-lifecycle.d.mts +30 -1
- package/dist/git/branch-lifecycle.d.mts.map +1 -1
- package/dist/git/branch-lifecycle.mjs +4 -5
- package/dist/git/transaction.d.mts +2 -2
- package/dist/git/transaction.d.mts.map +1 -1
- package/dist/git/transaction.mjs +8 -8
- package/dist/{graph-builder-tM7zj-SX.mjs → graph-builder-DXv2JEp-.mjs} +3 -3
- package/dist/{graph-builder-tM7zj-SX.mjs.map → graph-builder-DXv2JEp-.mjs.map} +1 -1
- package/dist/{id-DV_T9Ic8.mjs → id-CIO9s0x_.mjs} +1 -1
- package/dist/{id-DV_T9Ic8.mjs.map → id-CIO9s0x_.mjs.map} +1 -1
- package/dist/index-D0wQJSgs.d.mts +2 -0
- package/dist/index.mjs +22 -17
- package/dist/index.mjs.map +1 -1
- package/dist/local-DvP4Y4T-.mjs +182 -0
- package/dist/local-DvP4Y4T-.mjs.map +1 -0
- package/dist/{meta-manager--0IwAPCl.mjs → meta-manager-C900rEyG.mjs} +2 -2
- package/dist/{meta-manager--0IwAPCl.mjs.map → meta-manager-C900rEyG.mjs.map} +1 -1
- package/dist/{model-manager-DW_fOGRM.mjs → model-manager-BhLsUgaB.mjs} +241 -49
- package/dist/model-manager-BhLsUgaB.mjs.map +1 -0
- package/dist/{node-Cbq5eR5i.mjs → node-DN9hZeil.mjs} +1 -1
- package/dist/{node-Cbq5eR5i.mjs.map → node-DN9hZeil.mjs.map} +1 -1
- package/dist/ops-DjEWi5Wa.mjs +486 -0
- package/dist/ops-DjEWi5Wa.mjs.map +1 -0
- package/dist/overlay-reader-DOdyDRIv.mjs +74 -0
- package/dist/overlay-reader-DOdyDRIv.mjs.map +1 -0
- package/dist/paths-C3xNyc7z.mjs +52 -0
- package/dist/paths-C3xNyc7z.mjs.map +1 -0
- package/dist/paths-CYl5vCBD.mjs +54 -0
- package/dist/paths-CYl5vCBD.mjs.map +1 -0
- package/dist/providers/github/index.d.mts +144 -0
- package/dist/providers/github/index.d.mts.map +1 -0
- package/dist/providers/github/index.mjs +414 -0
- package/dist/providers/github/index.mjs.map +1 -0
- package/dist/providers/gitlab/index.d.mts +158 -0
- package/dist/providers/gitlab/index.d.mts.map +1 -0
- package/dist/providers/gitlab/index.mjs +361 -0
- package/dist/providers/gitlab/index.mjs.map +1 -0
- package/dist/providers/local/index.d.mts +106 -0
- package/dist/providers/local/index.d.mts.map +1 -0
- package/dist/providers/local/index.mjs +12 -0
- package/dist/{scan-config-QgnJ1-52.mjs → scan-config-jC6XmiqF.mjs} +2 -2
- package/dist/{scan-config-QgnJ1-52.mjs.map → scan-config-jC6XmiqF.mjs.map} +1 -1
- package/dist/{scanner-DUoXnmtI.mjs → scanner-DKMwc03k.mjs} +7 -7
- package/dist/{scanner-DUoXnmtI.mjs.map → scanner-DKMwc03k.mjs.map} +1 -1
- package/dist/serialization-B1CEzR4H.mjs +2 -0
- package/dist/server/http/index.d.mts +65 -0
- package/dist/server/http/index.d.mts.map +1 -0
- package/dist/server/http/index.mjs +137 -0
- package/dist/server/http/index.mjs.map +1 -0
- package/dist/{server-Dtq8vqbK.mjs → server-B1OJrUQ9.mjs} +486 -355
- package/dist/server-B1OJrUQ9.mjs.map +1 -0
- package/dist/server-WqGx6ait.d.mts +50 -0
- package/dist/server-WqGx6ait.d.mts.map +1 -0
- package/dist/server.d.mts +2 -7
- package/dist/server.mjs +22 -17
- package/dist/{source-map-generator-DpO-ejrt.mjs → source-map-generator-BWDva49I.mjs} +1 -1
- package/dist/{source-map-generator-DpO-ejrt.mjs.map → source-map-generator-BWDva49I.mjs.map} +1 -1
- package/dist/{svelte-parser-1bBcMx3T.mjs → svelte-parser-Dr9mmZuk.mjs} +3 -3
- package/dist/{svelte-parser-1bBcMx3T.mjs.map → svelte-parser-Dr9mmZuk.mjs.map} +1 -1
- package/dist/templates/index.mjs +1 -1
- package/dist/{templates-bRPzYq4M.mjs → templates-VrLVqfe5.mjs} +1 -1
- package/dist/{templates-bRPzYq4M.mjs.map → templates-VrLVqfe5.mjs.map} +1 -1
- package/dist/tools/annotations.d.mts +24 -0
- package/dist/tools/annotations.d.mts.map +1 -0
- package/dist/tools/annotations.mjs +2 -0
- package/dist/{transaction-C_Pvux78.mjs → transaction-DgkNgWSI.mjs} +61 -32
- package/dist/transaction-DgkNgWSI.mjs.map +1 -0
- package/dist/tsx-parser-BOMZE_yQ.mjs +2 -0
- package/dist/{tsx-parser-BWiMGAIR.mjs → tsx-parser-CQc6nr-o.mjs} +1 -1
- package/dist/{tsx-parser-BWiMGAIR.mjs.map → tsx-parser-CQc6nr-o.mjs.map} +1 -1
- package/dist/util/detect.mjs +2 -3
- package/dist/util/fs.mjs +1 -2
- package/dist/util/id.mjs +1 -1
- package/dist/validator-B0nNRFeU.mjs +906 -0
- package/dist/validator-B0nNRFeU.mjs.map +1 -0
- package/dist/{vue-parser-DTPTN3Yb.mjs → vue-parser-COgjArqv.mjs} +3 -3
- package/dist/{vue-parser-DTPTN3Yb.mjs.map → vue-parser-COgjArqv.mjs.map} +1 -1
- package/package.json +75 -21
- package/dist/config-B5kc2oeV.mjs +0 -27
- package/dist/config-B5kc2oeV.mjs.map +0 -1
- package/dist/context-DIDt_TxX.mjs +0 -46
- package/dist/context-DIDt_TxX.mjs.map +0 -1
- package/dist/core/content-manager.d.mts.map +0 -1
- package/dist/core/validator.d.mts +0 -23
- package/dist/core/validator.d.mts.map +0 -1
- package/dist/core/validator.mjs +0 -7
- package/dist/fs-BnIQ4dKj.mjs.map +0 -1
- package/dist/model-manager-DW_fOGRM.mjs.map +0 -1
- package/dist/serializer-CnHTtmc1.mjs +0 -2
- package/dist/server-Dtq8vqbK.mjs.map +0 -1
- package/dist/server.d.mts.map +0 -1
- package/dist/transaction-C_Pvux78.mjs.map +0 -1
- package/dist/tsx-parser-BFLv292Y.mjs +0 -2
- package/dist/util/serializer.d.mts +0 -2
- package/dist/util/serializer.mjs +0 -2
- package/dist/validator-SQYUV2JP.mjs +0 -581
- package/dist/validator-SQYUV2JP.mjs.map +0 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { a as FileChange, d as RepoReader } from "../../index-D0wQJSgs.mjs";
|
|
2
|
+
import { t as ContentEntry } from "../../content-manager-DqDLSNQ-.mjs";
|
|
3
|
+
import { ContentrainConfig, ModelDefinition, Vocabulary } from "@contentrain/types";
|
|
4
|
+
|
|
5
|
+
//#region src/core/ops/apply-to-worktree.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Phase 2 shim — applies a `FileChange[]` to a local worktree directory.
|
|
8
|
+
*
|
|
9
|
+
* Phase 3 replaces this with `LocalProvider.applyPlan` (same logic, owned by
|
|
10
|
+
* the provider) while a `GitHubProvider.applyPlan` produces the equivalent
|
|
11
|
+
* commit through the Git Data API. Until then, tool handlers call this shim
|
|
12
|
+
* inside the existing `createTransaction` flow so Phase 2 gets plan/apply
|
|
13
|
+
* semantics without rewriting the transaction machinery.
|
|
14
|
+
*
|
|
15
|
+
* - `content: string` → `writeFile` (creates parent dirs).
|
|
16
|
+
* - `content: null` → `rm`; missing files are tolerated.
|
|
17
|
+
* - Changes are applied in their given order; callers should sort for
|
|
18
|
+
* determinism.
|
|
19
|
+
*/
|
|
20
|
+
declare function applyChangesToWorktree(worktree: string, changes: readonly FileChange[]): Promise<void>;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/core/ops/types.d.ts
|
|
23
|
+
/**
|
|
24
|
+
* Generic plan envelope. Every `planXxx` operation in `core/ops/` returns
|
|
25
|
+
* this shape: a set of atomic file changes plus a typed result payload. The
|
|
26
|
+
* caller (tool handler or Studio engine) applies the changes through a
|
|
27
|
+
* `RepoProvider` and uses `result` for the tool response.
|
|
28
|
+
*
|
|
29
|
+
* See `.internal/refactor/00-principles.md` §5.1 for the plan/apply pattern.
|
|
30
|
+
*/
|
|
31
|
+
interface OpPlan<TResult = unknown> {
|
|
32
|
+
/** File additions, modifications and deletions, sorted by path for determinism. */
|
|
33
|
+
changes: FileChange[];
|
|
34
|
+
/** Operation-specific result payload (IDs of entries touched, etc.). */
|
|
35
|
+
result: TResult;
|
|
36
|
+
/** Soft warnings the caller should surface to the user. */
|
|
37
|
+
advisories: string[];
|
|
38
|
+
}
|
|
39
|
+
interface ContentSaveEntryResult {
|
|
40
|
+
action: 'created' | 'updated';
|
|
41
|
+
id?: string;
|
|
42
|
+
slug?: string;
|
|
43
|
+
locale: string;
|
|
44
|
+
advisories?: string[];
|
|
45
|
+
}
|
|
46
|
+
type ContentSavePlan = OpPlan<ContentSaveEntryResult[]>;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/core/ops/content-delete.d.ts
|
|
49
|
+
interface ContentDeleteInput {
|
|
50
|
+
model: ModelDefinition;
|
|
51
|
+
id?: string;
|
|
52
|
+
slug?: string;
|
|
53
|
+
locale?: string;
|
|
54
|
+
keys?: string[];
|
|
55
|
+
}
|
|
56
|
+
type ContentDeletePlan = OpPlan<string[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Build the FileChange[] required to delete content. Pure — touches no disk.
|
|
59
|
+
*
|
|
60
|
+
* Semantics preserved from legacy `deleteContent`:
|
|
61
|
+
* - Collection: remove the entry ID from each locale's content file and its
|
|
62
|
+
* meta file. Meta file is deleted outright if the removal empties it.
|
|
63
|
+
* - Singleton: delete the locale's content + meta file (locale-scoped when
|
|
64
|
+
* i18n, non-i18n removes whatever meta lives under `.contentrain/meta/{id}/`).
|
|
65
|
+
* - Dictionary: two modes — key-scoped removal updates the file in place,
|
|
66
|
+
* or locale-scoped removes the content + meta pair.
|
|
67
|
+
* - Document: strategy-aware delete (file/suffix/directory/none), plus
|
|
68
|
+
* meta removal under `.contentrain/meta/{id}/{slug}/`.
|
|
69
|
+
*/
|
|
70
|
+
declare function planContentDelete(reader: RepoReader, input: ContentDeleteInput): Promise<ContentDeletePlan>;
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/core/ops/content-save.d.ts
|
|
73
|
+
interface PlanInput {
|
|
74
|
+
model: ModelDefinition;
|
|
75
|
+
entries: ContentEntry[];
|
|
76
|
+
config: ContentrainConfig;
|
|
77
|
+
vocabulary?: Vocabulary | null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build the FileChange[] required to save a batch of content entries. The
|
|
81
|
+
* plan is deterministic, pure, and does not touch disk — it reads existing
|
|
82
|
+
* state through `reader` and returns changes for a single atomic commit.
|
|
83
|
+
*
|
|
84
|
+
* Grouping: multiple entries targeting the same (model, locale) share a
|
|
85
|
+
* single content file and a single meta file. Reads are cached in-memory
|
|
86
|
+
* so repeated reads of the same path during a plan are one IO.
|
|
87
|
+
*/
|
|
88
|
+
declare function planContentSave(reader: RepoReader, input: PlanInput): Promise<ContentSavePlan>;
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/core/ops/model-delete.d.ts
|
|
91
|
+
interface ModelDeleteInput {
|
|
92
|
+
model: ModelDefinition;
|
|
93
|
+
}
|
|
94
|
+
type ModelDeletePlan = OpPlan<string[]>;
|
|
95
|
+
/**
|
|
96
|
+
* Build the FileChange[] required to delete a model and its associated
|
|
97
|
+
* content/meta files.
|
|
98
|
+
*
|
|
99
|
+
* The schema is at most two directory levels deep (e.g. document meta at
|
|
100
|
+
* `.contentrain/meta/{id}/{slug}/{locale}.json`). The helper below walks
|
|
101
|
+
* that much, which is enough for every model kind + locale strategy and
|
|
102
|
+
* keeps us free of a general-purpose recursive lister for now.
|
|
103
|
+
*
|
|
104
|
+
* `result` is the legacy three-entry summary:
|
|
105
|
+
* - `models/{id}.json`
|
|
106
|
+
* - `content/{domain}/{id}/` (only if any content files existed)
|
|
107
|
+
* - `meta/{id}/` (only if any meta files existed)
|
|
108
|
+
*/
|
|
109
|
+
declare function planModelDelete(reader: RepoReader, input: ModelDeleteInput): Promise<ModelDeletePlan>;
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/core/ops/model-save.d.ts
|
|
112
|
+
interface ModelSaveInput {
|
|
113
|
+
model: ModelDefinition;
|
|
114
|
+
}
|
|
115
|
+
interface ModelSaveResult {
|
|
116
|
+
action: 'created' | 'updated';
|
|
117
|
+
id: string;
|
|
118
|
+
}
|
|
119
|
+
type ModelSavePlan = OpPlan<ModelSaveResult>;
|
|
120
|
+
/**
|
|
121
|
+
* Build the FileChange[] required to create or update a model definition.
|
|
122
|
+
*
|
|
123
|
+
* The only disk change is the model JSON file itself. Content and meta
|
|
124
|
+
* directories are created on-demand when their first write lands — git does
|
|
125
|
+
* not track empty directories, and MCP's write helpers `mkdir -p` before
|
|
126
|
+
* every file write, so there is no operational reason to emit an empty-dir
|
|
127
|
+
* ensure step here.
|
|
128
|
+
*/
|
|
129
|
+
declare function planModelSave(reader: RepoReader, input: ModelSaveInput): Promise<ModelSavePlan>;
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/core/ops/paths.d.ts
|
|
132
|
+
declare function contentFilePath(model: Pick<ModelDefinition, 'id' | 'kind' | 'domain' | 'i18n' | 'content_path' | 'locale_strategy'>, locale: string): string;
|
|
133
|
+
declare function documentFilePath(model: Pick<ModelDefinition, 'id' | 'domain' | 'i18n' | 'content_path' | 'locale_strategy'>, locale: string, slug: string): string;
|
|
134
|
+
declare function metaFilePath(model: Pick<ModelDefinition, 'id' | 'kind'>, locale: string, slug?: string): string;
|
|
135
|
+
//#endregion
|
|
136
|
+
export { type ContentDeleteInput, type ContentDeletePlan, type ContentSaveEntryResult, type ContentSavePlan, type ModelDeleteInput, type ModelDeletePlan, type ModelSaveInput, type ModelSavePlan, type ModelSaveResult, type OpPlan, applyChangesToWorktree, contentFilePath, documentFilePath, metaFilePath, planContentDelete, planContentSave, planModelDelete, planModelSave };
|
|
137
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/ops/apply-to-worktree.ts","../../../src/core/ops/types.ts","../../../src/core/ops/content-delete.ts","../../../src/core/ops/content-save.ts","../../../src/core/ops/model-delete.ts","../../../src/core/ops/model-save.ts","../../../src/core/ops/paths.ts"],"mappings":";;;;;;;;;AAkBA;;;;;;;;;;iBAAsB,sBAAA,CAAuB,QAAA,UAAkB,OAAA,WAAkB,UAAA,KAAe,OAAA;;;;;;;AAAhG;;;;UCRiB,MAAA;EDQgE;ECN/E,OAAA,EAAS,UAAA;EDMqF;ECJ9F,MAAA,EAAQ,OAAA;EDI6F;ECFrG,UAAA;AAAA;AAAA,UAGe,sBAAA;EACf,MAAA;EACA,EAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,KAGU,eAAA,GAAkB,MAAA,CAAO,sBAAA;;;UCpBpB,kBAAA;EACf,KAAA,EAAO,eAAA;EACP,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,MAAA;;;;;;;;ADLhC;;;;;;iBC+CsB,iBAAA,CACpB,MAAA,EAAQ,UAAA,EACR,KAAA,EAAO,kBAAA,GACN,OAAA,CAAQ,iBAAA;;;UCpDD,SAAA;EACR,KAAA,EAAO,eAAA;EACP,OAAA,EAAS,YAAA;EACT,MAAA,EAAQ,iBAAA;EACR,UAAA,GAAa,UAAA;AAAA;;;;;;;;;AFFf;iBEcsB,eAAA,CAAgB,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,SAAA,GAAY,OAAA,CAAQ,eAAA;;;UCpBpE,gBAAA;EACf,KAAA,EAAO,eAAA;AAAA;AAAA,KAGG,eAAA,GAAkB,MAAA;;;;;;;;;;;;AHE9B;;;iBGcsB,eAAA,CAAgB,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,gBAAA,GAAmB,OAAA,CAAQ,eAAA;;;UCN3E,cAAA;EACf,KAAA,EAAO,eAAA;AAAA;AAAA,UAGQ,eAAA;EACf,MAAA;EACA,EAAA;AAAA;AAAA,KAGU,aAAA,GAAgB,MAAA,CAAO,eAAA;;;;;;;;AJjBnC;;iBI4BsB,aAAA,CAAc,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,cAAA,GAAiB,OAAA,CAAQ,aAAA;;;iBCPxE,eAAA,CACd,KAAA,EAAO,IAAA,CAAK,eAAA,2EACZ,MAAA;AAAA,iBAac,gBAAA,CACd,KAAA,EAAO,IAAA,CAAK,eAAA,kEACZ,MAAA,UACA,IAAA;AAAA,iBAac,YAAA,CACd,KAAA,EAAO,IAAA,CAAK,eAAA,kBACZ,MAAA,UACA,IAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { a as applyChangesToWorktree, i as planContentDelete, n as planModelDelete, r as planContentSave, t as planModelSave } from "../../ops-DjEWi5Wa.mjs";
|
|
2
|
+
import "../../serialization-B1CEzR4H.mjs";
|
|
3
|
+
import { i as metaFilePath, n as contentFilePath, r as documentFilePath } from "../../paths-CYl5vCBD.mjs";
|
|
4
|
+
export { applyChangesToWorktree, contentFilePath, documentFilePath, metaFilePath, planContentDelete, planContentSave, planModelDelete, planModelSave };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { a as FileChange, d as RepoReader } from "../index-D0wQJSgs.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/core/overlay-reader.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A RepoReader that overlays a set of pending FileChanges on top of an
|
|
6
|
+
* underlying reader. Used in the remote-provider write path so helpers
|
|
7
|
+
* like {@link import('./context.js').buildContextChange} and
|
|
8
|
+
* {@link import('./validator/project.js').validateProject} see the
|
|
9
|
+
* post-change state — the state the pending commit is about to produce
|
|
10
|
+
* — rather than the pre-change base branch.
|
|
11
|
+
*
|
|
12
|
+
* Semantics:
|
|
13
|
+
*
|
|
14
|
+
* - `readFile(path)` — returns pending `content` when the overlay maps
|
|
15
|
+
* the path; falls back to the base reader otherwise. A pending delete
|
|
16
|
+
* (`content: null`) surfaces as "missing" (throws, matching
|
|
17
|
+
* `RepoReader.readFile`'s missing-file contract).
|
|
18
|
+
*
|
|
19
|
+
* - `listDirectory(path)` — merges the base directory listing with
|
|
20
|
+
* pending additions that live directly in the same folder, removes
|
|
21
|
+
* entries whose pending change is a delete, and de-duplicates the
|
|
22
|
+
* result. Pending paths in nested subdirectories surface only at
|
|
23
|
+
* their own listings.
|
|
24
|
+
*
|
|
25
|
+
* - `fileExists(path)` — pending adds → `true`, pending deletes →
|
|
26
|
+
* `false`, otherwise delegates.
|
|
27
|
+
*
|
|
28
|
+
* The overlay keys are canonicalised to match the FileChange contract:
|
|
29
|
+
* forward slashes, no leading `/`, no `..` segments (FileChanges are
|
|
30
|
+
* required to respect these invariants).
|
|
31
|
+
*/
|
|
32
|
+
declare class OverlayReader implements RepoReader {
|
|
33
|
+
private readonly base;
|
|
34
|
+
private readonly overlay;
|
|
35
|
+
constructor(base: RepoReader, pendingChanges: FileChange[]);
|
|
36
|
+
readFile(path: string, ref?: string): Promise<string>;
|
|
37
|
+
listDirectory(path: string, ref?: string): Promise<string[]>;
|
|
38
|
+
fileExists(path: string, ref?: string): Promise<boolean>;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { OverlayReader };
|
|
42
|
+
//# sourceMappingURL=overlay-reader.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-reader.d.mts","names":[],"sources":["../../src/core/overlay-reader.ts"],"mappings":";;;;;AA8BA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,aAAA,YAAyB,UAAA;EAAA,iBAIjB,IAAA;EAAA,iBAHF,OAAA;cAGE,IAAA,EAAM,UAAA,EACvB,cAAA,EAAgB,UAAA;EAQZ,QAAA,CAAS,IAAA,UAAc,GAAA,YAAe,OAAA;EAYtC,aAAA,CAAc,IAAA,UAAc,GAAA,YAAe,OAAA;EA+B3C,UAAA,CAAW,IAAA,UAAc,GAAA,YAAe,OAAA;AAAA"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import "../
|
|
3
|
-
import { a as classifyFile, i as autoDetectSourceDirs, n as SCAN_EXTENSIONS, o as discoverFiles, r as SCAN_IGNORE_DIRS, t as MAX_SCAN_FILES } from "../scan-config-QgnJ1-52.mjs";
|
|
1
|
+
import "../fs-DLbVB-Ek.mjs";
|
|
2
|
+
import { a as classifyFile, i as autoDetectSourceDirs, n as SCAN_EXTENSIONS, o as discoverFiles, r as SCAN_IGNORE_DIRS, t as MAX_SCAN_FILES } from "../scan-config-jC6XmiqF.mjs";
|
|
4
3
|
export { MAX_SCAN_FILES, SCAN_EXTENSIONS, SCAN_IGNORE_DIRS, autoDetectSourceDirs, classifyFile, discoverFiles };
|
package/dist/core/scanner.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import "../
|
|
3
|
-
import "../
|
|
4
|
-
import
|
|
5
|
-
import "../tsx-parser-BWiMGAIR.mjs";
|
|
1
|
+
import "../fs-DLbVB-Ek.mjs";
|
|
2
|
+
import "../scan-config-jC6XmiqF.mjs";
|
|
3
|
+
import { n as scanSummary, t as scanCandidates } from "../scanner-DKMwc03k.mjs";
|
|
4
|
+
import "../tsx-parser-CQc6nr-o.mjs";
|
|
6
5
|
export { scanCandidates, scanSummary };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { d as RepoReader } from "../../index-D0wQJSgs.mjs";
|
|
2
|
+
import { EntryMeta, FieldDef, ModelDefinition, ValidationError, ValidationResult } from "@contentrain/types";
|
|
3
|
+
|
|
4
|
+
//#region src/core/validator/entry.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Context for cross-entry validation (unique, relations, nested references).
|
|
7
|
+
*
|
|
8
|
+
* Shape aligned with Studio's `content-validation.ts:ValidationContext` so
|
|
9
|
+
* Studio can swap its internal implementation for this module without
|
|
10
|
+
* touching call sites.
|
|
11
|
+
*/
|
|
12
|
+
interface ValidationContext {
|
|
13
|
+
/** All entries in the collection (for unique-constraint checks). Keyed by entry ID. */
|
|
14
|
+
allEntries?: Record<string, Record<string, unknown>>;
|
|
15
|
+
/** ID of the entry being validated (excluded from unique-constraint comparisons). */
|
|
16
|
+
currentEntryId?: string;
|
|
17
|
+
/** All model definitions, if available (reserved for future cross-model checks). */
|
|
18
|
+
models?: ModelDefinition[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validate a single content entry against its model's field schema.
|
|
22
|
+
*
|
|
23
|
+
* Merges the rule sets from MCP's legacy `validator.ts` (secret detection,
|
|
24
|
+
* schema validation, unique constraints) with Studio's `content-validation.ts`
|
|
25
|
+
* (email/url heuristics, polymorphic relation structure, nested object and
|
|
26
|
+
* array-of-object recursion). The union is the authoritative per-entry
|
|
27
|
+
* validator — both MCP's project validator and Studio's save path should
|
|
28
|
+
* converge on this function over time.
|
|
29
|
+
*
|
|
30
|
+
* Asynchronous relation-integrity checks (does the referenced entry exist?)
|
|
31
|
+
* live in `relation-integrity.ts` because they require I/O.
|
|
32
|
+
*/
|
|
33
|
+
declare function validateContent(data: Record<string, unknown>, fields: Record<string, FieldDef>, modelId: string, locale: string, entryId?: string, ctx?: ValidationContext): ValidationResult;
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/core/validator/relation-integrity.d.ts
|
|
36
|
+
/**
|
|
37
|
+
* Resolution of a target model for a relation integrity check.
|
|
38
|
+
*
|
|
39
|
+
* - `{ exists: true, content: {...} }` — target model is known and its
|
|
40
|
+
* content for the requested locale was loaded. The ref must appear
|
|
41
|
+
* as a key in `content`.
|
|
42
|
+
* - `{ exists: true, content: null }` — target model exists (e.g. a
|
|
43
|
+
* singleton or dictionary) but per-entry key enforcement does not
|
|
44
|
+
* apply. The checker skips the ref existence test for this target.
|
|
45
|
+
* - `{ exists: false }` — the referenced model is not defined at all.
|
|
46
|
+
* The checker emits a "target model not found" error.
|
|
47
|
+
*/
|
|
48
|
+
interface ResolvedTarget {
|
|
49
|
+
exists: boolean;
|
|
50
|
+
content?: Record<string, unknown> | null;
|
|
51
|
+
}
|
|
52
|
+
/** Back-compat loader signature used by Studio. A null return means "nothing to check". */
|
|
53
|
+
type LegacyLoadContent = (targetModelId: string, targetLocale: string) => Promise<Record<string, unknown> | null>;
|
|
54
|
+
interface CheckRelationIntegrityOptions {
|
|
55
|
+
/** Severity for broken-relation errors. Default: `warning` (Studio-compat). */
|
|
56
|
+
severity?: 'error' | 'warning';
|
|
57
|
+
/**
|
|
58
|
+
* Richer target resolver — when provided, it can signal target-model
|
|
59
|
+
* absence. MCP's project validator passes one so `contentrain_validate`
|
|
60
|
+
* still emits "target model not found" errors.
|
|
61
|
+
*/
|
|
62
|
+
resolveTarget?: (targetModelId: string, targetLocale: string) => Promise<ResolvedTarget>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Verify that relation and relations fields reference targets that actually
|
|
66
|
+
* exist. Two severities are supported — Studio's per-save flow emits
|
|
67
|
+
* `warning` (the referenced entry may still be drafted), while MCP's
|
|
68
|
+
* project-wide validator emits `error` and additionally flags missing
|
|
69
|
+
* target models via `resolveTarget`.
|
|
70
|
+
*
|
|
71
|
+
* The loader abstraction keeps this function I/O-agnostic: MCP wires it
|
|
72
|
+
* to filesystem reads through LocalReader, Studio wires it to a
|
|
73
|
+
* `GitProvider.readFile`-backed loader, mocks pass in-memory maps.
|
|
74
|
+
*/
|
|
75
|
+
declare function checkRelationIntegrity(data: Record<string, unknown>, fields: Record<string, FieldDef>, modelId: string, locale: string, entryId: string | undefined, loadContent: LegacyLoadContent, opts?: CheckRelationIntegrityOptions): Promise<ValidationError[]>;
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/core/validator/project.d.ts
|
|
78
|
+
interface ValidateOptions {
|
|
79
|
+
model?: string;
|
|
80
|
+
fix?: boolean;
|
|
81
|
+
}
|
|
82
|
+
interface ValidateResult {
|
|
83
|
+
valid: boolean;
|
|
84
|
+
summary: {
|
|
85
|
+
errors: number;
|
|
86
|
+
warnings: number;
|
|
87
|
+
notices: number;
|
|
88
|
+
models_checked: number;
|
|
89
|
+
entries_checked: number;
|
|
90
|
+
};
|
|
91
|
+
issues: ValidationError[];
|
|
92
|
+
fixed: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validate a project's content against its model schemas.
|
|
96
|
+
*
|
|
97
|
+
* Two signatures:
|
|
98
|
+
*
|
|
99
|
+
* - `validateProject(projectRoot, options?)` — legacy disk-backed flow.
|
|
100
|
+
* `options.fix: true` applies structural repairs (canonical sort,
|
|
101
|
+
* orphan meta, missing locale files) directly on disk.
|
|
102
|
+
* - `validateProject(reader, options?)` — reader-backed flow used by
|
|
103
|
+
* HTTP/remote callers (e.g. GitHubProvider). `options.fix` is ignored
|
|
104
|
+
* because remote flows cannot write to the local filesystem.
|
|
105
|
+
*
|
|
106
|
+
* Read-side behavior is identical across both signatures so Studio and
|
|
107
|
+
* local CLIs see the same issue set for the same content state.
|
|
108
|
+
*/
|
|
109
|
+
declare function validateProject(projectRoot: string, options?: ValidateOptions): Promise<ValidateResult>;
|
|
110
|
+
declare function validateProject(reader: RepoReader, options?: ValidateOptions): Promise<ValidateResult>;
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/core/validator/schedule.d.ts
|
|
113
|
+
/**
|
|
114
|
+
* Validate `publish_at` and `expire_at` meta fields.
|
|
115
|
+
*
|
|
116
|
+
* Rules (matches legacy `validator.ts:validateScheduleFields`):
|
|
117
|
+
* - `publish_at` must parse as a valid Date
|
|
118
|
+
* - `expire_at` must parse as a valid Date
|
|
119
|
+
* - When both are present, `expire_at` must be strictly after `publish_at`
|
|
120
|
+
*/
|
|
121
|
+
declare function validateScheduleFields(meta: EntryMeta, ctx: {
|
|
122
|
+
model: string;
|
|
123
|
+
locale: string;
|
|
124
|
+
entry?: string;
|
|
125
|
+
slug?: string;
|
|
126
|
+
}, issues: ValidationError[]): void;
|
|
127
|
+
//#endregion
|
|
128
|
+
export { type CheckRelationIntegrityOptions, type LegacyLoadContent, type ResolvedTarget, type ValidateOptions, type ValidateResult, type ValidationContext, checkRelationIntegrity, validateContent, validateProject, validateScheduleFields };
|
|
129
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/validator/entry.ts","../../../src/core/validator/relation-integrity.ts","../../../src/core/validator/project.ts","../../../src/core/validator/schedule.ts"],"mappings":";;;;;;;AAUA;;;;UAAiB,iBAAA;EAMN;EAJT,UAAA,GAAa,MAAA,SAAe,MAAA;EAIJ;EAFxB,cAAA;EAFa;EAIb,MAAA,GAAS,eAAA;AAAA;;;;;AAgBX;;;;;;;;;iBAAgB,eAAA,CACd,IAAA,EAAM,MAAA,mBACN,MAAA,EAAQ,MAAA,SAAe,QAAA,GACvB,OAAA,UACA,MAAA,UACA,OAAA,WACA,GAAA,GAAM,iBAAA,GACL,gBAAA;;;;;;AA7BH;;;;;;;;;UCIiB,cAAA;EACf,MAAA;EACA,OAAA,GAAU,MAAA;AAAA;;KAIA,iBAAA,IACV,aAAA,UACA,YAAA,aACG,OAAA,CAAQ,MAAA;AAAA,UAEI,6BAAA;EDOD;ECLd,QAAA;;;;;;EAMA,aAAA,IACE,aAAA,UACA,YAAA,aACG,OAAA,CAAQ,cAAA;AAAA;;;;;;;;;;;;iBAcO,sBAAA,CACpB,IAAA,EAAM,MAAA,mBACN,MAAA,EAAQ,MAAA,SAAe,QAAA,GACvB,OAAA,UACA,MAAA,UACA,OAAA,sBACA,WAAA,EAAa,iBAAA,EACb,IAAA,GAAM,6BAAA,GACL,OAAA,CAAQ,eAAA;;;UCxCM,eAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,UAGe,cAAA;EACf,KAAA;EACA,OAAA;IACE,MAAA;IACA,QAAA;IACA,OAAA;IACA,cAAA;IACA,eAAA;EAAA;EAEF,MAAA,EAAQ,eAAA;EACR,KAAA;AAAA;;;AFDF;;;;;;;;;;;;;iBEqrBsB,eAAA,CAAgB,WAAA,UAAqB,OAAA,GAAU,eAAA,GAAkB,OAAA,CAAQ,cAAA;AAAA,iBACzE,eAAA,CAAgB,MAAA,EAAQ,UAAA,EAAY,OAAA,GAAU,eAAA,GAAkB,OAAA,CAAQ,cAAA;;;;;;AF5sB9F;;;;;iBGAgB,sBAAA,CACd,IAAA,EAAM,SAAA,EACN,GAAA;EAAO,KAAA;EAAe,MAAA;EAAgB,KAAA;EAAgB,IAAA;AAAA,GACtD,MAAA,EAAQ,eAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "../../fs-DLbVB-Ek.mjs";
|
|
2
|
+
import "../../config-BIl5Ybj0.mjs";
|
|
3
|
+
import "../../model-manager-BhLsUgaB.mjs";
|
|
4
|
+
import "../../meta-manager-C900rEyG.mjs";
|
|
5
|
+
import { i as validateContent, n as validateScheduleFields, r as checkRelationIntegrity, t as validateProject } from "../../validator-B0nNRFeU.mjs";
|
|
6
|
+
export { checkRelationIntegrity, validateContent, validateProject, validateScheduleFields };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as readJson, r as pathExists } from "./fs-
|
|
1
|
+
import { a as readJson, r as pathExists } from "./fs-DLbVB-Ek.mjs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
//#region src/util/detect.ts
|
|
4
4
|
const STACK_META = {
|
|
@@ -359,4 +359,4 @@ async function collectAllDeps(projectRoot) {
|
|
|
359
359
|
//#endregion
|
|
360
360
|
export { detectStackInfo as n, inferPlatform as r, detectStack as t };
|
|
361
361
|
|
|
362
|
-
//# sourceMappingURL=detect-
|
|
362
|
+
//# sourceMappingURL=detect-DL9mFmaS.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-DinOQAmK.mjs","names":[],"sources":["../src/util/detect.ts"],"sourcesContent":["import type { StackType, Platform } from '@contentrain/types'\nimport { join, dirname } from 'node:path'\nimport { readJson, pathExists } from './fs.js'\n\ninterface PackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n workspaces?: string[] | { packages: string[] }\n}\n\nexport interface StackInfo {\n stack: StackType\n platform: Platform\n name: string\n description: string\n monorepo: boolean\n monorepoTool?: string\n features: string[]\n}\n\nconst STACK_META: Record<StackType, { name: string; description: string }> = {\n // Meta-frameworks\n nuxt: { name: 'Nuxt', description: 'Vue meta-framework with SSR/SSG' },\n next: { name: 'Next.js', description: 'React framework with SSR/SSG' },\n astro: { name: 'Astro', description: 'Content-first web framework' },\n sveltekit: { name: 'SvelteKit', description: 'Svelte meta-framework' },\n remix: { name: 'Remix', description: 'Full-stack React framework' },\n analog: { name: 'Analog', description: 'Angular meta-framework' },\n // Plain frameworks\n vue: { name: 'Vue', description: 'Progressive JavaScript framework' },\n react: { name: 'React', description: 'UI library for web and native' },\n svelte: { name: 'Svelte', description: 'Compile-time UI framework' },\n solid: { name: 'Solid', description: 'Reactive UI library' },\n angular: { name: 'Angular', description: 'Platform for web applications' },\n // Mobile\n 'react-native': { name: 'React Native', description: 'Cross-platform mobile framework' },\n expo: { name: 'Expo', description: 'React Native toolchain' },\n flutter: { name: 'Flutter', description: 'Cross-platform UI toolkit (Dart)' },\n // Backend\n node: { name: 'Node.js', description: 'Server-side JavaScript runtime' },\n express: { name: 'Express', description: 'Minimal Node.js web framework' },\n fastify: { name: 'Fastify', description: 'Fast Node.js web framework' },\n nestjs: { name: 'NestJS', description: 'Progressive Node.js framework' },\n django: { name: 'Django', description: 'Python web framework' },\n rails: { name: 'Rails', description: 'Ruby web framework' },\n laravel: { name: 'Laravel', description: 'PHP web framework' },\n go: { name: 'Go', description: 'Go programming language' },\n rust: { name: 'Rust', description: 'Rust programming language' },\n dotnet: { name: '.NET', description: 'Microsoft .NET platform' },\n // Static\n hugo: { name: 'Hugo', description: 'Go-based static site generator' },\n jekyll: { name: 'Jekyll', description: 'Ruby-based static site generator' },\n eleventy: { name: 'Eleventy', description: 'JavaScript static site generator' },\n // Desktop\n electron: { name: 'Electron', description: 'Cross-platform desktop apps' },\n tauri: { name: 'Tauri', description: 'Lightweight desktop apps (Rust)' },\n // Catch-all\n other: { name: 'Other', description: 'Unknown or unsupported framework' },\n}\n\nexport function inferPlatform(stack: StackType): Platform {\n switch (stack) {\n case 'nuxt': case 'next': case 'astro': case 'sveltekit': case 'remix': case 'analog':\n case 'vue': case 'react': case 'svelte': case 'solid': case 'angular':\n return 'web'\n case 'react-native': case 'expo': case 'flutter':\n return 'mobile'\n case 'node': case 'express': case 'fastify': case 'nestjs':\n case 'django': case 'rails': case 'laravel': case 'go': case 'rust': case 'dotnet':\n return 'api'\n case 'electron': case 'tauri':\n return 'desktop'\n case 'hugo': case 'jekyll': case 'eleventy':\n return 'static'\n default:\n return 'other'\n }\n}\n\n/** Backward-compatible: returns just the StackType string */\nexport async function detectStack(projectRoot: string): Promise<StackType> {\n const info = await detectStackInfo(projectRoot)\n return info.stack\n}\n\n/** Rich detection: stack + platform + monorepo + features */\nexport async function detectStackInfo(projectRoot: string): Promise<StackInfo> {\n let stack = await detectFromDeps(projectRoot)\n\n // Walk up for monorepo root if local didn't match\n if (stack === 'other') {\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n const pkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (pkg?.workspaces) {\n const rootStack = await detectFromDeps(dir)\n if (rootStack !== 'other') {\n stack = rootStack\n break\n }\n }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n }\n\n // Non-JS detection if still 'other'\n if (stack === 'other') {\n stack = await detectNonJs(projectRoot)\n }\n\n const mono = await detectMonorepo(projectRoot)\n const features = await detectFeatures(projectRoot)\n const meta = STACK_META[stack]\n\n return {\n stack,\n platform: inferPlatform(stack),\n name: meta.name,\n description: meta.description,\n monorepo: mono.isMonorepo,\n monorepoTool: mono.tool,\n features,\n }\n}\n\nasync function detectFromDeps(dir: string): Promise<StackType> {\n const pkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (!pkg) return 'other'\n\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }\n\n // Meta-frameworks (check BEFORE plain frameworks)\n if ('nuxt' in allDeps) return 'nuxt'\n if ('next' in allDeps) return 'next'\n if ('astro' in allDeps) return 'astro'\n if ('@sveltejs/kit' in allDeps) return 'sveltekit'\n if ('@remix-run/node' in allDeps || 'remix' in allDeps) return 'remix'\n if ('@analogjs/platform' in allDeps) return 'analog'\n\n // Mobile (check before plain react)\n if ('expo' in allDeps) return 'expo'\n if ('react-native' in allDeps) return 'react-native'\n\n // Plain frameworks\n if ('vue' in allDeps) return 'vue'\n if ('svelte' in allDeps) return 'svelte'\n if ('solid-js' in allDeps) return 'solid'\n if ('@angular/core' in allDeps) return 'angular'\n if ('react' in allDeps) return 'react'\n\n // Desktop\n if ('electron' in allDeps) return 'electron'\n if ('@tauri-apps/api' in allDeps) return 'tauri'\n\n // Backend (Node.js)\n if ('@nestjs/core' in allDeps) return 'nestjs'\n if ('fastify' in allDeps) return 'fastify'\n if ('express' in allDeps) return 'express'\n\n // Static site generators\n if ('@11ty/eleventy' in allDeps) return 'eleventy'\n\n // Generic Node.js (has package.json but no framework)\n if ('node' in (pkg.dependencies ?? {})) return 'node'\n\n return 'other'\n}\n\nasync function detectNonJs(projectRoot: string): Promise<StackType> {\n // Go\n if (await pathExists(join(projectRoot, 'go.mod'))) return 'go'\n\n // Rust\n if (await pathExists(join(projectRoot, 'Cargo.toml'))) return 'rust'\n\n // Python\n for (const f of ['requirements.txt', 'pyproject.toml', 'setup.py']) {\n if (await pathExists(join(projectRoot, f))) {\n // Check for Django\n try {\n const { readText } = await import('./fs.js')\n const content = await readText(join(projectRoot, f))\n if (content?.includes('django') || content?.includes('Django')) return 'django'\n } catch { /* ignore */ }\n return 'other' // Generic Python — no specific stack type yet\n }\n }\n\n // Ruby\n if (await pathExists(join(projectRoot, 'Gemfile'))) {\n try {\n const { readText } = await import('./fs.js')\n const content = await readText(join(projectRoot, 'Gemfile'))\n if (content?.includes('rails')) return 'rails'\n } catch { /* ignore */ }\n return 'other'\n }\n\n // .NET\n const dotnetFiles = ['*.csproj', '*.sln']\n for (const pattern of dotnetFiles) {\n if (pattern === '*.csproj' && await pathExists(join(projectRoot, 'Program.cs'))) return 'dotnet'\n if (pattern === '*.sln' && await pathExists(join(projectRoot, 'Program.cs'))) return 'dotnet'\n }\n\n // PHP / Laravel\n if (await pathExists(join(projectRoot, 'composer.json'))) {\n try {\n const composer = await readJson<Record<string, unknown>>(join(projectRoot, 'composer.json'))\n const require = composer?.require as Record<string, string> | undefined\n if (require?.['laravel/framework']) return 'laravel'\n } catch { /* ignore */ }\n return 'other'\n }\n\n // Flutter (Dart)\n if (await pathExists(join(projectRoot, 'pubspec.yaml'))) return 'flutter'\n\n // Hugo\n if (await pathExists(join(projectRoot, 'hugo.toml'))) return 'hugo'\n if (await pathExists(join(projectRoot, 'config.toml'))) {\n try {\n const { readText } = await import('./fs.js')\n const content = await readText(join(projectRoot, 'config.toml'))\n if (content?.includes('baseURL') || content?.includes('hugo')) return 'hugo'\n } catch { /* ignore */ }\n }\n\n // Jekyll\n if (await pathExists(join(projectRoot, '_config.yml'))) return 'jekyll'\n\n return 'other'\n}\n\nasync function detectMonorepo(projectRoot: string): Promise<{ isMonorepo: boolean; tool?: string }> {\n if (await pathExists(join(projectRoot, 'pnpm-workspace.yaml'))) return { isMonorepo: true, tool: 'pnpm workspaces' }\n if (await pathExists(join(projectRoot, 'lerna.json'))) return { isMonorepo: true, tool: 'Lerna' }\n if (await pathExists(join(projectRoot, 'nx.json'))) return { isMonorepo: true, tool: 'Nx' }\n if (await pathExists(join(projectRoot, 'turbo.json'))) return { isMonorepo: true, tool: 'Turborepo' }\n\n const pkg = await readJson<PackageJson>(join(projectRoot, 'package.json'))\n if (pkg?.workspaces) return { isMonorepo: true, tool: 'npm/yarn workspaces' }\n\n // Walk up\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n if (await pathExists(join(dir, 'pnpm-workspace.yaml'))) return { isMonorepo: true, tool: 'pnpm workspaces' }\n if (await pathExists(join(dir, 'lerna.json'))) return { isMonorepo: true, tool: 'Lerna' }\n if (await pathExists(join(dir, 'nx.json'))) return { isMonorepo: true, tool: 'Nx' }\n if (await pathExists(join(dir, 'turbo.json'))) return { isMonorepo: true, tool: 'Turborepo' }\n const parentPkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (parentPkg?.workspaces) return { isMonorepo: true, tool: 'npm/yarn workspaces' }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n\n return { isMonorepo: false }\n}\n\nasync function detectFeatures(projectRoot: string): Promise<string[]> {\n const features: string[] = []\n const allDeps = await collectAllDeps(projectRoot)\n\n if ('typescript' in allDeps || await pathExists(join(projectRoot, 'tsconfig.json'))) features.push('TypeScript')\n\n const i18nLibs = ['vue-i18n', '@nuxtjs/i18n', 'next-intl', 'react-intl', 'i18next', 'react-i18next']\n for (const lib of i18nLibs) {\n if (lib in allDeps) { features.push(`i18n (${lib})`); break }\n }\n\n if ('tailwindcss' in allDeps) features.push('Tailwind CSS')\n else if ('sass' in allDeps || 'scss' in allDeps) features.push('Sass/SCSS')\n\n if ('vitest' in allDeps) features.push('Vitest')\n else if ('jest' in allDeps) features.push('Jest')\n\n if ('pinia' in allDeps) features.push('Pinia')\n else if ('zustand' in allDeps) features.push('Zustand')\n else if ('@reduxjs/toolkit' in allDeps || 'redux' in allDeps) features.push('Redux')\n\n return features\n}\n\nasync function collectAllDeps(projectRoot: string): Promise<Record<string, string>> {\n const pkg = await readJson<PackageJson>(join(projectRoot, 'package.json'))\n const allDeps: Record<string, string> = { ...pkg?.dependencies, ...pkg?.devDependencies }\n\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n const parentPkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (parentPkg?.workspaces) {\n Object.assign(allDeps, parentPkg.dependencies, parentPkg.devDependencies)\n break\n }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n\n return allDeps\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"detect-DL9mFmaS.mjs","names":[],"sources":["../src/util/detect.ts"],"sourcesContent":["import type { StackType, Platform } from '@contentrain/types'\nimport { join, dirname } from 'node:path'\nimport { readJson, pathExists } from './fs.js'\n\ninterface PackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n workspaces?: string[] | { packages: string[] }\n}\n\nexport interface StackInfo {\n stack: StackType\n platform: Platform\n name: string\n description: string\n monorepo: boolean\n monorepoTool?: string\n features: string[]\n}\n\nconst STACK_META: Record<StackType, { name: string; description: string }> = {\n // Meta-frameworks\n nuxt: { name: 'Nuxt', description: 'Vue meta-framework with SSR/SSG' },\n next: { name: 'Next.js', description: 'React framework with SSR/SSG' },\n astro: { name: 'Astro', description: 'Content-first web framework' },\n sveltekit: { name: 'SvelteKit', description: 'Svelte meta-framework' },\n remix: { name: 'Remix', description: 'Full-stack React framework' },\n analog: { name: 'Analog', description: 'Angular meta-framework' },\n // Plain frameworks\n vue: { name: 'Vue', description: 'Progressive JavaScript framework' },\n react: { name: 'React', description: 'UI library for web and native' },\n svelte: { name: 'Svelte', description: 'Compile-time UI framework' },\n solid: { name: 'Solid', description: 'Reactive UI library' },\n angular: { name: 'Angular', description: 'Platform for web applications' },\n // Mobile\n 'react-native': { name: 'React Native', description: 'Cross-platform mobile framework' },\n expo: { name: 'Expo', description: 'React Native toolchain' },\n flutter: { name: 'Flutter', description: 'Cross-platform UI toolkit (Dart)' },\n // Backend\n node: { name: 'Node.js', description: 'Server-side JavaScript runtime' },\n express: { name: 'Express', description: 'Minimal Node.js web framework' },\n fastify: { name: 'Fastify', description: 'Fast Node.js web framework' },\n nestjs: { name: 'NestJS', description: 'Progressive Node.js framework' },\n django: { name: 'Django', description: 'Python web framework' },\n rails: { name: 'Rails', description: 'Ruby web framework' },\n laravel: { name: 'Laravel', description: 'PHP web framework' },\n go: { name: 'Go', description: 'Go programming language' },\n rust: { name: 'Rust', description: 'Rust programming language' },\n dotnet: { name: '.NET', description: 'Microsoft .NET platform' },\n // Static\n hugo: { name: 'Hugo', description: 'Go-based static site generator' },\n jekyll: { name: 'Jekyll', description: 'Ruby-based static site generator' },\n eleventy: { name: 'Eleventy', description: 'JavaScript static site generator' },\n // Desktop\n electron: { name: 'Electron', description: 'Cross-platform desktop apps' },\n tauri: { name: 'Tauri', description: 'Lightweight desktop apps (Rust)' },\n // Catch-all\n other: { name: 'Other', description: 'Unknown or unsupported framework' },\n}\n\nexport function inferPlatform(stack: StackType): Platform {\n switch (stack) {\n case 'nuxt': case 'next': case 'astro': case 'sveltekit': case 'remix': case 'analog':\n case 'vue': case 'react': case 'svelte': case 'solid': case 'angular':\n return 'web'\n case 'react-native': case 'expo': case 'flutter':\n return 'mobile'\n case 'node': case 'express': case 'fastify': case 'nestjs':\n case 'django': case 'rails': case 'laravel': case 'go': case 'rust': case 'dotnet':\n return 'api'\n case 'electron': case 'tauri':\n return 'desktop'\n case 'hugo': case 'jekyll': case 'eleventy':\n return 'static'\n default:\n return 'other'\n }\n}\n\n/** Backward-compatible: returns just the StackType string */\nexport async function detectStack(projectRoot: string): Promise<StackType> {\n const info = await detectStackInfo(projectRoot)\n return info.stack\n}\n\n/** Rich detection: stack + platform + monorepo + features */\nexport async function detectStackInfo(projectRoot: string): Promise<StackInfo> {\n let stack = await detectFromDeps(projectRoot)\n\n // Walk up for monorepo root if local didn't match\n if (stack === 'other') {\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n const pkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (pkg?.workspaces) {\n const rootStack = await detectFromDeps(dir)\n if (rootStack !== 'other') {\n stack = rootStack\n break\n }\n }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n }\n\n // Non-JS detection if still 'other'\n if (stack === 'other') {\n stack = await detectNonJs(projectRoot)\n }\n\n const mono = await detectMonorepo(projectRoot)\n const features = await detectFeatures(projectRoot)\n const meta = STACK_META[stack]\n\n return {\n stack,\n platform: inferPlatform(stack),\n name: meta.name,\n description: meta.description,\n monorepo: mono.isMonorepo,\n monorepoTool: mono.tool,\n features,\n }\n}\n\nasync function detectFromDeps(dir: string): Promise<StackType> {\n const pkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (!pkg) return 'other'\n\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }\n\n // Meta-frameworks (check BEFORE plain frameworks)\n if ('nuxt' in allDeps) return 'nuxt'\n if ('next' in allDeps) return 'next'\n if ('astro' in allDeps) return 'astro'\n if ('@sveltejs/kit' in allDeps) return 'sveltekit'\n if ('@remix-run/node' in allDeps || 'remix' in allDeps) return 'remix'\n if ('@analogjs/platform' in allDeps) return 'analog'\n\n // Mobile (check before plain react)\n if ('expo' in allDeps) return 'expo'\n if ('react-native' in allDeps) return 'react-native'\n\n // Plain frameworks\n if ('vue' in allDeps) return 'vue'\n if ('svelte' in allDeps) return 'svelte'\n if ('solid-js' in allDeps) return 'solid'\n if ('@angular/core' in allDeps) return 'angular'\n if ('react' in allDeps) return 'react'\n\n // Desktop\n if ('electron' in allDeps) return 'electron'\n if ('@tauri-apps/api' in allDeps) return 'tauri'\n\n // Backend (Node.js)\n if ('@nestjs/core' in allDeps) return 'nestjs'\n if ('fastify' in allDeps) return 'fastify'\n if ('express' in allDeps) return 'express'\n\n // Static site generators\n if ('@11ty/eleventy' in allDeps) return 'eleventy'\n\n // Generic Node.js (has package.json but no framework)\n if ('node' in (pkg.dependencies ?? {})) return 'node'\n\n return 'other'\n}\n\nasync function detectNonJs(projectRoot: string): Promise<StackType> {\n // Go\n if (await pathExists(join(projectRoot, 'go.mod'))) return 'go'\n\n // Rust\n if (await pathExists(join(projectRoot, 'Cargo.toml'))) return 'rust'\n\n // Python\n for (const f of ['requirements.txt', 'pyproject.toml', 'setup.py']) {\n if (await pathExists(join(projectRoot, f))) {\n // Check for Django\n try {\n const { readText } = await import('./fs.js')\n const content = await readText(join(projectRoot, f))\n if (content?.includes('django') || content?.includes('Django')) return 'django'\n } catch { /* ignore */ }\n return 'other' // Generic Python — no specific stack type yet\n }\n }\n\n // Ruby\n if (await pathExists(join(projectRoot, 'Gemfile'))) {\n try {\n const { readText } = await import('./fs.js')\n const content = await readText(join(projectRoot, 'Gemfile'))\n if (content?.includes('rails')) return 'rails'\n } catch { /* ignore */ }\n return 'other'\n }\n\n // .NET\n const dotnetFiles = ['*.csproj', '*.sln']\n for (const pattern of dotnetFiles) {\n if (pattern === '*.csproj' && await pathExists(join(projectRoot, 'Program.cs'))) return 'dotnet'\n if (pattern === '*.sln' && await pathExists(join(projectRoot, 'Program.cs'))) return 'dotnet'\n }\n\n // PHP / Laravel\n if (await pathExists(join(projectRoot, 'composer.json'))) {\n try {\n const composer = await readJson<Record<string, unknown>>(join(projectRoot, 'composer.json'))\n const require = composer?.require as Record<string, string> | undefined\n if (require?.['laravel/framework']) return 'laravel'\n } catch { /* ignore */ }\n return 'other'\n }\n\n // Flutter (Dart)\n if (await pathExists(join(projectRoot, 'pubspec.yaml'))) return 'flutter'\n\n // Hugo\n if (await pathExists(join(projectRoot, 'hugo.toml'))) return 'hugo'\n if (await pathExists(join(projectRoot, 'config.toml'))) {\n try {\n const { readText } = await import('./fs.js')\n const content = await readText(join(projectRoot, 'config.toml'))\n if (content?.includes('baseURL') || content?.includes('hugo')) return 'hugo'\n } catch { /* ignore */ }\n }\n\n // Jekyll\n if (await pathExists(join(projectRoot, '_config.yml'))) return 'jekyll'\n\n return 'other'\n}\n\nasync function detectMonorepo(projectRoot: string): Promise<{ isMonorepo: boolean; tool?: string }> {\n if (await pathExists(join(projectRoot, 'pnpm-workspace.yaml'))) return { isMonorepo: true, tool: 'pnpm workspaces' }\n if (await pathExists(join(projectRoot, 'lerna.json'))) return { isMonorepo: true, tool: 'Lerna' }\n if (await pathExists(join(projectRoot, 'nx.json'))) return { isMonorepo: true, tool: 'Nx' }\n if (await pathExists(join(projectRoot, 'turbo.json'))) return { isMonorepo: true, tool: 'Turborepo' }\n\n const pkg = await readJson<PackageJson>(join(projectRoot, 'package.json'))\n if (pkg?.workspaces) return { isMonorepo: true, tool: 'npm/yarn workspaces' }\n\n // Walk up\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n if (await pathExists(join(dir, 'pnpm-workspace.yaml'))) return { isMonorepo: true, tool: 'pnpm workspaces' }\n if (await pathExists(join(dir, 'lerna.json'))) return { isMonorepo: true, tool: 'Lerna' }\n if (await pathExists(join(dir, 'nx.json'))) return { isMonorepo: true, tool: 'Nx' }\n if (await pathExists(join(dir, 'turbo.json'))) return { isMonorepo: true, tool: 'Turborepo' }\n const parentPkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (parentPkg?.workspaces) return { isMonorepo: true, tool: 'npm/yarn workspaces' }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n\n return { isMonorepo: false }\n}\n\nasync function detectFeatures(projectRoot: string): Promise<string[]> {\n const features: string[] = []\n const allDeps = await collectAllDeps(projectRoot)\n\n if ('typescript' in allDeps || await pathExists(join(projectRoot, 'tsconfig.json'))) features.push('TypeScript')\n\n const i18nLibs = ['vue-i18n', '@nuxtjs/i18n', 'next-intl', 'react-intl', 'i18next', 'react-i18next']\n for (const lib of i18nLibs) {\n if (lib in allDeps) { features.push(`i18n (${lib})`); break }\n }\n\n if ('tailwindcss' in allDeps) features.push('Tailwind CSS')\n else if ('sass' in allDeps || 'scss' in allDeps) features.push('Sass/SCSS')\n\n if ('vitest' in allDeps) features.push('Vitest')\n else if ('jest' in allDeps) features.push('Jest')\n\n if ('pinia' in allDeps) features.push('Pinia')\n else if ('zustand' in allDeps) features.push('Zustand')\n else if ('@reduxjs/toolkit' in allDeps || 'redux' in allDeps) features.push('Redux')\n\n return features\n}\n\nasync function collectAllDeps(projectRoot: string): Promise<Record<string, string>> {\n const pkg = await readJson<PackageJson>(join(projectRoot, 'package.json'))\n const allDeps: Record<string, string> = { ...pkg?.dependencies, ...pkg?.devDependencies }\n\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n const parentPkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (parentPkg?.workspaces) {\n Object.assign(allDeps, parentPkg.dependencies, parentPkg.devDependencies)\n break\n }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n\n return allDeps\n}\n"],"mappings":"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"}
|