@coordiation/agent 0.1.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/LICENSE +21 -0
- package/README.md +14 -0
- package/agent-manifest.schema.json +34 -0
- package/context-packs.json +48 -0
- package/package.json +27 -0
- package/src/index.js +207 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Coordiation contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @coordiation/agent
|
|
2
|
+
|
|
3
|
+
Compact, deterministic project context for AI agents using Coordiation.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { createContextPack, inspectProject } from "@coordiation/agent";
|
|
7
|
+
|
|
8
|
+
const manifest = await inspectProject({ cwd: process.cwd() });
|
|
9
|
+
const pricingContext = createContextPack("pricing", manifest);
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The compact manifest reports the active framework, Coordiation packages, utility families, frequent literal classes, project rules, and trackable warnings without sending the entire source tree to an agent.
|
|
13
|
+
|
|
14
|
+
Available context packs: `page`, `navigation`, `form`, `dashboard`, `pricing`, `component`, and `theme`.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://coordiation.com/schemas/agent-manifest.json",
|
|
4
|
+
"title": "Coordiation Agent Manifest",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schemaVersion", "kind", "project", "coordiation", "contracts", "tracking"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schemaVersion": { "const": "1.0" },
|
|
9
|
+
"kind": { "const": "coordiation-agent-manifest" },
|
|
10
|
+
"project": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["name", "framework", "packageManager"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"name": { "type": "string" },
|
|
15
|
+
"framework": { "type": "string" },
|
|
16
|
+
"packageManager": { "type": "string" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"coordiation": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"required": ["prefix", "packages", "utilityCount", "utilityFamilies"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"prefix": { "type": "string" },
|
|
24
|
+
"packages": { "type": "array", "items": { "type": "string" } },
|
|
25
|
+
"utilityCount": { "type": "integer", "minimum": 0 },
|
|
26
|
+
"utilityFamilies": { "type": "object", "additionalProperties": { "type": "integer" } }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"contracts": { "type": "object" },
|
|
30
|
+
"tracking": { "type": "object" },
|
|
31
|
+
"contextBudget": { "type": "object" }
|
|
32
|
+
},
|
|
33
|
+
"additionalProperties": true
|
|
34
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./agent-manifest.schema.json",
|
|
3
|
+
"schemaVersion": "1.0",
|
|
4
|
+
"packs": {
|
|
5
|
+
"page": {
|
|
6
|
+
"intent": "Build a complete responsive page from semantic sections.",
|
|
7
|
+
"components": ["button", "card", "separator", "typography"],
|
|
8
|
+
"utilityHints": ["co-grid", "co-flex", "co-gap-6", "co-w-full", "co-max-w-screen-xl"],
|
|
9
|
+
"checks": ["semantic landmarks", "responsive layout", "visible focus", "no horizontal overflow"]
|
|
10
|
+
},
|
|
11
|
+
"navigation": {
|
|
12
|
+
"intent": "Build accessible desktop and mobile navigation.",
|
|
13
|
+
"components": ["button", "navigation-menu", "sheet"],
|
|
14
|
+
"utilityHints": ["co-flex", "co-items-center", "co-justify-between", "co-hidden", "md:co-flex"],
|
|
15
|
+
"checks": ["keyboard access", "mobile trigger label", "active state", "focus return"]
|
|
16
|
+
},
|
|
17
|
+
"form": {
|
|
18
|
+
"intent": "Build an accessible validated form with literal styling states.",
|
|
19
|
+
"components": ["button", "field", "input", "label", "select", "textarea"],
|
|
20
|
+
"utilityHints": ["co-grid", "co-gap-4", "focus:co-ring-2", "disabled:co-opacity-50"],
|
|
21
|
+
"checks": ["label association", "error description", "disabled state", "keyboard submission"]
|
|
22
|
+
},
|
|
23
|
+
"dashboard": {
|
|
24
|
+
"intent": "Build a dense responsive application workspace.",
|
|
25
|
+
"components": ["avatar", "badge", "card", "data-table", "progress", "sidebar"],
|
|
26
|
+
"utilityHints": ["co-grid", "lg:co-grid-cols-12", "co-gap-4", "co-overflow-x-auto"],
|
|
27
|
+
"checks": ["mobile sidebar", "table overflow", "status semantics", "readable data density"]
|
|
28
|
+
},
|
|
29
|
+
"pricing": {
|
|
30
|
+
"intent": "Build a comparable pricing section with a clear recommended plan.",
|
|
31
|
+
"components": ["badge", "button", "card", "separator"],
|
|
32
|
+
"utilityHints": ["co-grid", "md:co-grid-cols-3", "co-gap-5", "co-border", "co-ring-2"],
|
|
33
|
+
"checks": ["comparable features", "recommended label", "CTA contrast", "maximum three-line titles"]
|
|
34
|
+
},
|
|
35
|
+
"component": {
|
|
36
|
+
"intent": "Create one reusable Coordiation-native UI primitive.",
|
|
37
|
+
"components": ["button", "label"],
|
|
38
|
+
"utilityHints": ["co-inline-flex", "co-items-center", "co-transition", "focus-visible:co-ring-2"],
|
|
39
|
+
"checks": ["typed API", "all interaction states", "accessible name", "reduced motion"]
|
|
40
|
+
},
|
|
41
|
+
"theme": {
|
|
42
|
+
"intent": "Create an installable open-code application theme.",
|
|
43
|
+
"components": ["button", "card", "ThemeIcon"],
|
|
44
|
+
"utilityHints": ["co-grid", "co-flex", "co-transition", "motion-reduce:co-animate-none"],
|
|
45
|
+
"checks": ["namespaced CSS", "Coordiation icons", "Coordiation copyright", "registry contract", "responsive preview"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coordiation/agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Compact project manifests and task context packs for AI agents using Coordiation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./context-packs.json": "./context-packs.json",
|
|
10
|
+
"./agent-manifest.schema.json": "./agent-manifest.schema.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"context-packs.json",
|
|
15
|
+
"agent-manifest.schema.json",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://coordiation.com/docs/tooling/agent-context",
|
|
26
|
+
"author": "Wiryo Saputra <wiryosaputra@coordiation.com>"
|
|
27
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import contextPackRegistry from "../context-packs.json" with { type: "json" };
|
|
5
|
+
|
|
6
|
+
export const AGENT_SCHEMA_VERSION = "1.0";
|
|
7
|
+
export const AGENT_MANIFEST_PATH = ".coordiation/agent-manifest.json";
|
|
8
|
+
export const contextPacks = contextPackRegistry.packs;
|
|
9
|
+
|
|
10
|
+
const SOURCE_EXTENSIONS = new Set([".astro", ".blade.php", ".coord", ".css", ".html", ".htm", ".js", ".jsx", ".md", ".mdx", ".php", ".svelte", ".ts", ".tsx", ".vue"]);
|
|
11
|
+
const DEFAULT_SOURCE_ROOTS = ["app", "src", "pages", "components", "resources", "templates", "packages", "site/app"];
|
|
12
|
+
const IGNORED_DIRECTORIES = new Set([".git", ".next", ".output", ".vinext", "build", "coverage", "dist", "node_modules", "vendor"]);
|
|
13
|
+
const UTILITY_PATTERN = /(?:[a-z0-9-]+:)*!?-?co-[a-z0-9][a-z0-9_./:[\]()%#,-]*/gi;
|
|
14
|
+
|
|
15
|
+
async function exists(pathname) {
|
|
16
|
+
try { await access(pathname, constants.F_OK); return true; } catch { return false; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function readJson(pathname, fallback = {}) {
|
|
20
|
+
try { return JSON.parse(await readFile(pathname, "utf8")); } catch { return fallback; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function allDependencies(packageJson) {
|
|
24
|
+
return { ...(packageJson.dependencies ?? {}), ...(packageJson.devDependencies ?? {}), ...(packageJson.peerDependencies ?? {}) };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function detectFramework(dependencies) {
|
|
28
|
+
if (dependencies.next) return "Next.js";
|
|
29
|
+
if (dependencies.astro) return "Astro";
|
|
30
|
+
if (dependencies["@sveltejs/kit"] || dependencies.svelte) return "SvelteKit";
|
|
31
|
+
if (dependencies.nuxt || dependencies.vue) return dependencies.nuxt ? "Nuxt" : "Vue";
|
|
32
|
+
if (dependencies["@angular/core"]) return "Angular";
|
|
33
|
+
if (dependencies.react) return "React";
|
|
34
|
+
if (dependencies.vite) return "Vite";
|
|
35
|
+
return "framework-agnostic";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function detectPackageManager(cwd) {
|
|
39
|
+
if (await exists(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
40
|
+
if (await exists(join(cwd, "yarn.lock"))) return "yarn";
|
|
41
|
+
if (await exists(join(cwd, "bun.lock")) || await exists(join(cwd, "bun.lockb"))) return "bun";
|
|
42
|
+
if (await exists(join(cwd, "package-lock.json"))) return "npm";
|
|
43
|
+
return "unknown";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isSourceFile(name) {
|
|
47
|
+
if (name.endsWith(".blade.php")) return true;
|
|
48
|
+
return SOURCE_EXTENSIONS.has(extname(name).toLowerCase());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function collectSourceFiles(cwd, maxFiles) {
|
|
52
|
+
const files = [];
|
|
53
|
+
async function walk(directory) {
|
|
54
|
+
if (files.length >= maxFiles) return;
|
|
55
|
+
let entries;
|
|
56
|
+
try { entries = await readdir(directory, { withFileTypes: true }); } catch { return; }
|
|
57
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (files.length >= maxFiles) return;
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
if (!IGNORED_DIRECTORIES.has(entry.name) && !entry.name.startsWith(".")) await walk(join(directory, entry.name));
|
|
62
|
+
} else if (entry.isFile() && isSourceFile(entry.name)) files.push(join(directory, entry.name));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const root of DEFAULT_SOURCE_ROOTS) {
|
|
66
|
+
const directory = join(cwd, root);
|
|
67
|
+
if (await exists(directory)) await walk(directory);
|
|
68
|
+
}
|
|
69
|
+
return files;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function utilityFamily(candidate) {
|
|
73
|
+
const base = candidate.slice(candidate.lastIndexOf(":") + 1).replace(/^!/, "").replace(/^-/, "").replace(/^co-/, "");
|
|
74
|
+
if (base.startsWith("bg-")) return "backgrounds";
|
|
75
|
+
if (base.startsWith("text-") || base.startsWith("font-") || base.startsWith("leading-") || base.startsWith("tracking-")) return "typography";
|
|
76
|
+
if (/^(m|p|gap|space)-/.test(base)) return "spacing";
|
|
77
|
+
if (/^(w|h|min-|max-|size-)/.test(base)) return "sizing";
|
|
78
|
+
if (/^(grid|col-|row-|flex|items-|justify-|content-|place-)/.test(base)) return "flex-grid";
|
|
79
|
+
if (/^(border|rounded|ring)/.test(base)) return "borders-rings";
|
|
80
|
+
if (/^(shadow|opacity|mix-blend|blur|filter)/.test(base)) return "effects";
|
|
81
|
+
if (/^(transition|duration|ease|animate|delay)/.test(base)) return "transitions";
|
|
82
|
+
if (/^(translate|rotate|scale|skew|transform)/.test(base)) return "transforms";
|
|
83
|
+
if (/^(cursor|pointer-events|select-|resize|scroll|touch)/.test(base)) return "interaction";
|
|
84
|
+
return "layout";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function compactPath(cwd, pathname) {
|
|
88
|
+
return relative(cwd, pathname).split(sep).join("/");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function inspectProject({ cwd = process.cwd(), full = false, maxFiles = 2000 } = {}) {
|
|
92
|
+
const root = resolve(cwd);
|
|
93
|
+
const packageJson = await readJson(join(root, "package.json"), { name: root.split(sep).at(-1) });
|
|
94
|
+
const dependencies = allDependencies(packageJson);
|
|
95
|
+
const files = await collectSourceFiles(root, maxFiles);
|
|
96
|
+
const utilityFrequency = new Map();
|
|
97
|
+
const componentImports = new Set();
|
|
98
|
+
let dynamicCandidateCount = 0;
|
|
99
|
+
let unreadableFileCount = 0;
|
|
100
|
+
|
|
101
|
+
for (const filename of files) {
|
|
102
|
+
let source;
|
|
103
|
+
try { source = await readFile(filename, "utf8"); } catch { unreadableFileCount += 1; continue; }
|
|
104
|
+
for (const match of source.matchAll(UTILITY_PATTERN)) {
|
|
105
|
+
const preceding = source[(match.index ?? 0) - 1];
|
|
106
|
+
if (preceding === "-" || preceding === "@") continue;
|
|
107
|
+
if (match[0].endsWith("-")) continue;
|
|
108
|
+
const candidate = match[0].replace(/[),.;]+$/, "");
|
|
109
|
+
utilityFrequency.set(candidate, (utilityFrequency.get(candidate) ?? 0) + 1);
|
|
110
|
+
}
|
|
111
|
+
dynamicCandidateCount += (source.match(/class(?:Name)?\s*=\s*[^\n]{0,80}co-[^\n]{0,32}\$\{/g) ?? []).length;
|
|
112
|
+
for (const match of source.matchAll(/from\s+["'](@coordiation\/[^"']+)["']/g)) componentImports.add(match[1]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const utilities = [...utilityFrequency.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
116
|
+
const utilityFamilies = {};
|
|
117
|
+
for (const [candidate] of utilities) {
|
|
118
|
+
const family = utilityFamily(candidate);
|
|
119
|
+
utilityFamilies[family] = (utilityFamilies[family] ?? 0) + 1;
|
|
120
|
+
}
|
|
121
|
+
const packages = [...new Set([
|
|
122
|
+
...Object.keys(dependencies).filter((name) => name.startsWith("@coordiation/")),
|
|
123
|
+
...[...componentImports].map((name) => name.split("/").slice(0, 2).join("/")),
|
|
124
|
+
])].sort();
|
|
125
|
+
const manifest = {
|
|
126
|
+
schemaVersion: AGENT_SCHEMA_VERSION,
|
|
127
|
+
kind: "coordiation-agent-manifest",
|
|
128
|
+
project: {
|
|
129
|
+
name: packageJson.name ?? root.split(sep).at(-1),
|
|
130
|
+
framework: detectFramework(dependencies),
|
|
131
|
+
packageManager: await detectPackageManager(root),
|
|
132
|
+
},
|
|
133
|
+
coordiation: {
|
|
134
|
+
prefix: "co-",
|
|
135
|
+
packages,
|
|
136
|
+
componentImports: [...componentImports].sort(),
|
|
137
|
+
utilityCount: utilities.length,
|
|
138
|
+
utilityFamilies: Object.fromEntries(Object.entries(utilityFamilies).sort(([a], [b]) => a.localeCompare(b))),
|
|
139
|
+
frequentUtilities: utilities.slice(0, 30).map(([candidate, count]) => ({ candidate, count })),
|
|
140
|
+
},
|
|
141
|
+
contracts: {
|
|
142
|
+
sourceOwnership: "open-code",
|
|
143
|
+
classStrategy: "literal-static-candidates",
|
|
144
|
+
browserRuntime: 0,
|
|
145
|
+
responsive: "required",
|
|
146
|
+
accessibility: "required",
|
|
147
|
+
motion: "respect-prefers-reduced-motion",
|
|
148
|
+
icons: "use-Coordiation-icon-components",
|
|
149
|
+
copyright: "Coordiation",
|
|
150
|
+
},
|
|
151
|
+
tracking: {
|
|
152
|
+
scannedFiles: files.length,
|
|
153
|
+
scanLimitReached: files.length >= maxFiles,
|
|
154
|
+
unreadableFiles: unreadableFileCount,
|
|
155
|
+
dynamicCandidateCount,
|
|
156
|
+
warnings: dynamicCandidateCount ? ["Dynamic co-* candidates may be invisible to the compiler. Replace interpolation with literal lookup values."] : [],
|
|
157
|
+
},
|
|
158
|
+
commands: {
|
|
159
|
+
addComponent: "coordiation add component <name>",
|
|
160
|
+
addTheme: "coordiation add theme <name>",
|
|
161
|
+
context: "coordiation context <topic> --json",
|
|
162
|
+
refreshManifest: "coordiation inspect --write",
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
if (full) {
|
|
166
|
+
manifest.coordiation.utilities = utilities.map(([candidate, count]) => ({ candidate, count }));
|
|
167
|
+
manifest.tracking.sourceFiles = files.map((filename) => compactPath(root, filename));
|
|
168
|
+
}
|
|
169
|
+
const bytes = Buffer.byteLength(JSON.stringify(manifest));
|
|
170
|
+
manifest.contextBudget = { mode: full ? "full" : "compact", bytes, estimatedTokens: Math.ceil(bytes / 4) };
|
|
171
|
+
return manifest;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function createContextPack(topic, manifest) {
|
|
175
|
+
const normalized = String(topic ?? "").trim().toLowerCase();
|
|
176
|
+
const definition = contextPacks[normalized];
|
|
177
|
+
if (!definition) throw new Error(`Unknown context topic: ${topic}. Available: ${Object.keys(contextPacks).join(", ")}`);
|
|
178
|
+
const currentUtilities = manifest?.coordiation?.frequentUtilities ?? [];
|
|
179
|
+
const relevantUtilities = currentUtilities.filter(({ candidate }) => definition.utilityHints.some((hint) => candidate.includes(hint.replace(/^.*:/, "").replace(/^co-/, "")))).slice(0, 15);
|
|
180
|
+
return {
|
|
181
|
+
schemaVersion: AGENT_SCHEMA_VERSION,
|
|
182
|
+
kind: "coordiation-context-pack",
|
|
183
|
+
topic: normalized,
|
|
184
|
+
intent: definition.intent,
|
|
185
|
+
project: manifest ? { name: manifest.project.name, framework: manifest.project.framework, prefix: manifest.coordiation.prefix } : undefined,
|
|
186
|
+
components: definition.components,
|
|
187
|
+
utilityHints: definition.utilityHints,
|
|
188
|
+
currentUtilities: relevantUtilities,
|
|
189
|
+
rules: manifest?.contracts ?? {
|
|
190
|
+
classStrategy: "literal-static-candidates",
|
|
191
|
+
responsive: "required",
|
|
192
|
+
accessibility: "required",
|
|
193
|
+
icons: "use-Coordiation-icon-components",
|
|
194
|
+
},
|
|
195
|
+
checks: definition.checks,
|
|
196
|
+
workflow: ["inspect project", "reuse registered primitives", "generate literal classes", "validate checks", "run project build"],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function writeAgentManifest(manifest, { cwd = process.cwd(), output = AGENT_MANIFEST_PATH } = {}) {
|
|
201
|
+
const destination = resolve(cwd, output);
|
|
202
|
+
const root = resolve(cwd);
|
|
203
|
+
if (destination !== root && !destination.startsWith(`${root}${sep}`)) throw new Error(`Manifest output leaves the project: ${output}`);
|
|
204
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
205
|
+
await writeFile(destination, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
206
|
+
return compactPath(root, destination);
|
|
207
|
+
}
|