frontend-auto-cms 1.0.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 +64 -0
- package/dist/cli/apply.d.ts +7 -0
- package/dist/cli/apply.js +102 -0
- package/dist/cli/apply.js.map +1 -0
- package/dist/cli/fs-utils.d.ts +5 -0
- package/dist/cli/fs-utils.js +54 -0
- package/dist/cli/fs-utils.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +32 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/postinstall.d.ts +1 -0
- package/dist/cli/postinstall.js +10 -0
- package/dist/cli/postinstall.js.map +1 -0
- package/dist/cli/scanner.d.ts +2 -0
- package/dist/cli/scanner.js +97 -0
- package/dist/cli/scanner.js.map +1 -0
- package/dist/cli/setup.d.ts +6 -0
- package/dist/cli/setup.js +256 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/runtime/dashboard.js +1189 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/runtime/dashboard.d.ts +2 -0
- package/dist/runtime/runtime/dom-scan.d.ts +3 -0
- package/dist/runtime/runtime/i18n.d.ts +7 -0
- package/dist/runtime/runtime/index.d.ts +1 -0
- package/dist/runtime/runtime/publish.d.ts +11 -0
- package/dist/runtime/runtime/store.d.ts +7 -0
- package/dist/runtime/runtime/types.d.ts +8 -0
- package/dist/runtime/shared/constants.d.ts +10 -0
- package/dist/runtime/shared/hash.d.ts +4 -0
- package/dist/runtime/shared/types.d.ts +71 -0
- package/dist/shared/constants.d.ts +10 -0
- package/dist/shared/constants.js +11 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/hash.d.ts +4 -0
- package/dist/shared/hash.js +19 -0
- package/dist/shared/hash.js.map +1 -0
- package/dist/shared/types.d.ts +71 -0
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 frontend-auto-cms
|
|
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,64 @@
|
|
|
1
|
+
# frontend-auto-cms
|
|
2
|
+
|
|
3
|
+
Turn any frontend app into a mini-CMS with one import and a dedicated editor route.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install frontend-auto-cms
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Postinstall runs a setup wizard that:
|
|
12
|
+
|
|
13
|
+
- asks for a passcode,
|
|
14
|
+
- scans project files (`.html`, `.jsx`, `.tsx`, `.vue`, `.astro`, `.svelte`),
|
|
15
|
+
- generates `cms-content.json`, `.cms-config.json`, and `public/cms-runtime-auth.json`.
|
|
16
|
+
- optionally configures GitHub/GitLab publishing in `.cms-hosting.json` + `public/cms-hosting.json`.
|
|
17
|
+
- asks for a custom dashboard route and auto-detects editable pages into `public/cms-settings.json`.
|
|
18
|
+
- token is entered manually in dashboard at publish time (not stored in config).
|
|
19
|
+
|
|
20
|
+
## Use in app
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import "frontend-auto-cms";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or:
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<script type="module" src="/node_modules/frontend-auto-cms/dist/runtime/dashboard.js"></script>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Run your app and open your configured dashboard route (default: `/dashboard`).
|
|
33
|
+
|
|
34
|
+
The dashboard has:
|
|
35
|
+
- page tabs (About, Contact, Privacy, Terms, etc.),
|
|
36
|
+
- live preview iframe,
|
|
37
|
+
- one unified ordered editor list for non-technical users.
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
- `npx frontend-auto-cms setup` - rerun scanner and regenerate content baseline.
|
|
42
|
+
- `npx frontend-auto-cms apply` - apply `cms-export.patch.json` generated from dashboard.
|
|
43
|
+
- `npx frontend-auto-cms doctor` - quick installation check.
|
|
44
|
+
|
|
45
|
+
## i18n Local AI
|
|
46
|
+
|
|
47
|
+
Install optional peer:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install @xenova/transformers
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
In the CMS i18n tab, click **Auto-translate all languages** to generate locale dictionaries in memory, then export patch and apply.
|
|
54
|
+
|
|
55
|
+
## Persistence
|
|
56
|
+
|
|
57
|
+
- Live edits apply immediately to DOM.
|
|
58
|
+
- Content and locales are cached in `localStorage`.
|
|
59
|
+
- Save + Publish pushes changes to configured hosting provider or downloads `cms-export.patch.json` as fallback.
|
|
60
|
+
- CLI apply writes source replacements and locale JSON files.
|
|
61
|
+
|
|
62
|
+
## Security note
|
|
63
|
+
|
|
64
|
+
Browser runtime passcode gate is intentionally lightweight for local editing workflows. Provider token is entered manually for each publish and is not stored, but is still handled in browser memory during that action. Prefer short-lived tokens with restricted repo scope or move publishing behind a secure backend proxy.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { resolve, dirname, relative, isAbsolute } from "node:path";
|
|
2
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { CMS_PATCH_FILE, CMS_CONTENT_FILE, CMS_RUNTIME_LOCALES_FILE } from "../shared/constants.js";
|
|
6
|
+
function assertInsideRoot(root, targetPath, label) {
|
|
7
|
+
const rel = relative(root, targetPath);
|
|
8
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
9
|
+
throw new Error(`Refusing to access ${label} outside project root: ${targetPath}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function stableStringify(value) {
|
|
13
|
+
const normalize = (input) => {
|
|
14
|
+
if (Array.isArray(input)) {
|
|
15
|
+
return input.map(normalize);
|
|
16
|
+
}
|
|
17
|
+
if (input && typeof input === "object") {
|
|
18
|
+
const entries = Object.entries(input).sort(([a], [b]) => a.localeCompare(b));
|
|
19
|
+
return Object.fromEntries(entries.map(([k, v]) => [k, normalize(v)]));
|
|
20
|
+
}
|
|
21
|
+
return input;
|
|
22
|
+
};
|
|
23
|
+
return JSON.stringify(normalize(value));
|
|
24
|
+
}
|
|
25
|
+
function sha256Hex(value) {
|
|
26
|
+
return createHash("sha256").update(value).digest("hex");
|
|
27
|
+
}
|
|
28
|
+
async function replaceNth(haystack, needle, replacement, occurrence = 1) {
|
|
29
|
+
if (!needle) {
|
|
30
|
+
return haystack;
|
|
31
|
+
}
|
|
32
|
+
let from = 0;
|
|
33
|
+
let count = 0;
|
|
34
|
+
while (from <= haystack.length) {
|
|
35
|
+
const idx = haystack.indexOf(needle, from);
|
|
36
|
+
if (idx === -1) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
count += 1;
|
|
40
|
+
if (count === occurrence) {
|
|
41
|
+
return `${haystack.slice(0, idx)}${replacement}${haystack.slice(idx + needle.length)}`;
|
|
42
|
+
}
|
|
43
|
+
from = idx + needle.length;
|
|
44
|
+
}
|
|
45
|
+
return haystack;
|
|
46
|
+
}
|
|
47
|
+
export async function applyPatch(options = {}) {
|
|
48
|
+
const cwd = options.cwd ?? process.cwd();
|
|
49
|
+
const patchPath = resolve(cwd, options.patchPath ?? CMS_PATCH_FILE);
|
|
50
|
+
assertInsideRoot(cwd, patchPath, "patch file");
|
|
51
|
+
const patchRaw = await readFile(patchPath, "utf8");
|
|
52
|
+
const patch = JSON.parse(patchRaw);
|
|
53
|
+
if (!patch.integrity || patch.integrity.algorithm !== "sha256" || !patch.integrity.value) {
|
|
54
|
+
if (!options.allowUnsigned) {
|
|
55
|
+
throw new Error("Patch integrity metadata missing. Refusing to apply unsigned patch without --allow-unsigned.");
|
|
56
|
+
}
|
|
57
|
+
process.stdout.write(chalk.yellow("Warning: applying unsigned patch because --allow-unsigned was set.\n"));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const unsignedPatch = {
|
|
61
|
+
generatedAt: patch.generatedAt,
|
|
62
|
+
content: patch.content,
|
|
63
|
+
operations: patch.operations,
|
|
64
|
+
locales: patch.locales
|
|
65
|
+
};
|
|
66
|
+
const expected = sha256Hex(stableStringify(unsignedPatch));
|
|
67
|
+
if (expected !== patch.integrity.value) {
|
|
68
|
+
throw new Error("Patch integrity verification failed. File may be tampered or corrupted.");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
let appliedOps = 0;
|
|
72
|
+
for (const op of patch.operations) {
|
|
73
|
+
const filePath = resolve(cwd, op.file);
|
|
74
|
+
assertInsideRoot(cwd, filePath, "operation target");
|
|
75
|
+
const original = await readFile(filePath, "utf8");
|
|
76
|
+
const next = await replaceNth(original, op.find, op.replace, op.occurrence ?? 1);
|
|
77
|
+
if (next !== original) {
|
|
78
|
+
await writeFile(filePath, next, "utf8");
|
|
79
|
+
appliedOps += 1;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const contentPath = resolve(cwd, CMS_CONTENT_FILE);
|
|
83
|
+
await writeFile(contentPath, JSON.stringify(patch.content, null, 2), "utf8");
|
|
84
|
+
if (patch.locales) {
|
|
85
|
+
for (const [lang, dict] of Object.entries(patch.locales)) {
|
|
86
|
+
if (!/^[a-z0-9_-]{2,16}$/i.test(lang)) {
|
|
87
|
+
throw new Error(`Invalid locale code in patch: ${lang}`);
|
|
88
|
+
}
|
|
89
|
+
const target = resolve(cwd, "locales", `${lang}.json`);
|
|
90
|
+
assertInsideRoot(cwd, target, "locale file");
|
|
91
|
+
await mkdir(dirname(target), { recursive: true });
|
|
92
|
+
await writeFile(target, JSON.stringify(dict, null, 2), "utf8");
|
|
93
|
+
}
|
|
94
|
+
const runtimeLocalesPath = resolve(cwd, CMS_RUNTIME_LOCALES_FILE);
|
|
95
|
+
assertInsideRoot(cwd, runtimeLocalesPath, "runtime locales file");
|
|
96
|
+
await mkdir(dirname(runtimeLocalesPath), { recursive: true });
|
|
97
|
+
await writeFile(runtimeLocalesPath, JSON.stringify({ version: 1, locales: patch.locales }, null, 2), "utf8");
|
|
98
|
+
}
|
|
99
|
+
process.stdout.write(chalk.green(`Applied ${appliedOps} source operations.\n`));
|
|
100
|
+
process.stdout.write(chalk.green(`Updated ${CMS_CONTENT_FILE} and locale files.\n`));
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/cli/apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function walkProject(root: string): Promise<string[]>;
|
|
2
|
+
export declare function ensureDir(path: string): Promise<void>;
|
|
3
|
+
export declare function exists(path: string): Promise<boolean>;
|
|
4
|
+
export declare function readText(path: string): Promise<string>;
|
|
5
|
+
export declare function writeJson(path: string, data: unknown): Promise<void>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readdir, stat, readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { join, extname } from "node:path";
|
|
3
|
+
import { SUPPORTED_EXTENSIONS } from "../shared/constants.js";
|
|
4
|
+
const IGNORE_DIRS = new Set([
|
|
5
|
+
"node_modules",
|
|
6
|
+
".git",
|
|
7
|
+
"dist",
|
|
8
|
+
"build",
|
|
9
|
+
".next",
|
|
10
|
+
".astro",
|
|
11
|
+
".svelte-kit",
|
|
12
|
+
"coverage",
|
|
13
|
+
".turbo",
|
|
14
|
+
".vite"
|
|
15
|
+
]);
|
|
16
|
+
export async function walkProject(root) {
|
|
17
|
+
const out = [];
|
|
18
|
+
async function visit(dir) {
|
|
19
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const abs = join(dir, entry.name);
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
if (!IGNORE_DIRS.has(entry.name)) {
|
|
24
|
+
await visit(abs);
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (entry.isFile() && SUPPORTED_EXTENSIONS.includes(extname(entry.name))) {
|
|
29
|
+
out.push(abs);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
await visit(root);
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
export async function ensureDir(path) {
|
|
37
|
+
await mkdir(path, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
export async function exists(path) {
|
|
40
|
+
try {
|
|
41
|
+
await stat(path);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function readText(path) {
|
|
49
|
+
return readFile(path, "utf8");
|
|
50
|
+
}
|
|
51
|
+
export async function writeJson(path, data) {
|
|
52
|
+
await writeFile(path, JSON.stringify(data, null, 2), "utf8");
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=fs-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-utils.js","sourceRoot":"","sources":["../../src/cli/fs-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,cAAc;IACd,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,QAAQ;IACR,aAAa;IACb,UAAU;IACV,QAAQ;IACR,OAAO;CACR,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,MAAM,GAAG,GAAa,EAAE,CAAC;IAEzB,KAAK,UAAU,KAAK,CAAC,GAAW;QAC9B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;oBACjC,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;gBACnB,CAAC;gBACD,SAAS;YACX,CAAC;YACD,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,oBAAoB,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAA0C,CAAC,EAAE,CAAC;gBAClH,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY;IAC1C,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAY;IACvC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY;IACzC,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,IAAa;IACzD,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { setupCms } from "./setup.js";
|
|
5
|
+
import { applyPatch } from "./apply.js";
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name("frontend-auto-cms")
|
|
9
|
+
.description("Auto-inject CMS editing into any frontend project.")
|
|
10
|
+
.version("1.0.0");
|
|
11
|
+
program
|
|
12
|
+
.command("setup")
|
|
13
|
+
.description("Initialize CMS config/content files by scanning source files.")
|
|
14
|
+
.action(async () => {
|
|
15
|
+
await setupCms();
|
|
16
|
+
});
|
|
17
|
+
program
|
|
18
|
+
.command("apply")
|
|
19
|
+
.description("Apply patch generated from the in-browser dashboard.")
|
|
20
|
+
.option("-p, --patch <path>", "Patch file path", "cms-export.patch.json")
|
|
21
|
+
.option("--allow-unsigned", "Allow applying patches without integrity metadata", false)
|
|
22
|
+
.action(async (opts) => {
|
|
23
|
+
await applyPatch({ patchPath: opts.patch, allowUnsigned: opts.allowUnsigned });
|
|
24
|
+
});
|
|
25
|
+
program
|
|
26
|
+
.command("doctor")
|
|
27
|
+
.description("Verify package wiring quickly.")
|
|
28
|
+
.action(() => {
|
|
29
|
+
process.stdout.write(chalk.green("frontend-auto-cms CLI is installed and ready.\n"));
|
|
30
|
+
});
|
|
31
|
+
void program.parseAsync(process.argv);
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,mBAAmB,CAAC;KACzB,WAAW,CAAC,oDAAoD,CAAC;KACjE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,+DAA+D,CAAC;KAC5E,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,QAAQ,EAAE,CAAC;AACnB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,sDAAsD,CAAC;KACnE,MAAM,CAAC,oBAAoB,EAAE,iBAAiB,EAAE,uBAAuB,CAAC;KACxE,MAAM,CAAC,kBAAkB,EAAE,mDAAmD,EAAE,KAAK,CAAC;KACtF,MAAM,CAAC,KAAK,EAAE,IAA+C,EAAE,EAAE;IAChE,MAAM,UAAU,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;AACjF,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,gCAAgC,CAAC;KAC7C,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC,CAAC;AACvF,CAAC,CAAC,CAAC;AAEL,KAAK,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postinstall.js","sourceRoot":"","sources":["../../src/cli/postinstall.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,KAAK,UAAU,GAAG;IAChB,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;IACT,CAAC;AACH,CAAC;AAED,KAAK,GAAG,EAAE,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import { readText } from "./fs-utils.js";
|
|
3
|
+
let idCounter = 0;
|
|
4
|
+
function nextId(prefix) {
|
|
5
|
+
idCounter += 1;
|
|
6
|
+
return `${prefix}_${String(idCounter).padStart(4, "0")}`;
|
|
7
|
+
}
|
|
8
|
+
function normalizeText(text) {
|
|
9
|
+
return text.replace(/\s+/g, " ").trim();
|
|
10
|
+
}
|
|
11
|
+
function buildKey(file, type, idx) {
|
|
12
|
+
const clean = file.replace(/[^\w]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
13
|
+
return `${clean}.${type}.${idx}`;
|
|
14
|
+
}
|
|
15
|
+
function detectSections(content) {
|
|
16
|
+
const results = [];
|
|
17
|
+
const patterns = [
|
|
18
|
+
/<([a-zA-Z0-9_-]+)[^>]*class=["'][^"']*(faq|card|testimonial|item|list|feature|pricing)[^"']*["'][^>]*>[\s\S]*?<\/\1>/gi,
|
|
19
|
+
/\{[\s\S]*?map\(([\s\S]*?)=>[\s\S]*?\)/g
|
|
20
|
+
];
|
|
21
|
+
for (const pattern of patterns) {
|
|
22
|
+
let match;
|
|
23
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
24
|
+
const value = normalizeText(match[0]).slice(0, 240);
|
|
25
|
+
if (value.length > 30) {
|
|
26
|
+
results.push(value);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return Array.from(new Set(results));
|
|
31
|
+
}
|
|
32
|
+
export async function scanFiles(root, files) {
|
|
33
|
+
const nodes = [];
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
const rel = relative(root, file);
|
|
36
|
+
const content = await readText(file);
|
|
37
|
+
let idx = 0;
|
|
38
|
+
const textPattern = />\s*([^<>{}\n][^<>{}]{2,})\s*</g;
|
|
39
|
+
let textMatch;
|
|
40
|
+
while ((textMatch = textPattern.exec(content)) !== null) {
|
|
41
|
+
const textValue = normalizeText(textMatch[1]);
|
|
42
|
+
if (textValue.length < 2 || /^(true|false|null|undefined)$/i.test(textValue)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
idx += 1;
|
|
46
|
+
nodes.push({
|
|
47
|
+
id: nextId("txt"),
|
|
48
|
+
key: buildKey(rel, "text", idx),
|
|
49
|
+
type: "text",
|
|
50
|
+
label: `${rel} text ${idx}`,
|
|
51
|
+
value: textValue,
|
|
52
|
+
sourceRefs: [{ file: rel, original: textMatch[1] }]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
let mediaIndex = 0;
|
|
56
|
+
const mediaPattern = /<(img|video)\b([^>]*?)>/gi;
|
|
57
|
+
let mediaMatch;
|
|
58
|
+
while ((mediaMatch = mediaPattern.exec(content)) !== null) {
|
|
59
|
+
mediaIndex += 1;
|
|
60
|
+
const tag = mediaMatch[1].toLowerCase();
|
|
61
|
+
const attrs = mediaMatch[2];
|
|
62
|
+
const src = /src=["']([^"']+)["']/i.exec(attrs)?.[1] ?? "";
|
|
63
|
+
if (!src) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const alt = /alt=["']([^"']*)["']/i.exec(attrs)?.[1] ?? "";
|
|
67
|
+
const type = tag === "img" ? "image" : "video";
|
|
68
|
+
nodes.push({
|
|
69
|
+
id: nextId("med"),
|
|
70
|
+
key: buildKey(rel, type, mediaIndex),
|
|
71
|
+
type,
|
|
72
|
+
label: `${rel} ${type} ${mediaIndex}`,
|
|
73
|
+
value: src,
|
|
74
|
+
attrs: { alt, src },
|
|
75
|
+
sourceRefs: [{ file: rel, original: src }]
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const sections = detectSections(content);
|
|
79
|
+
for (let s = 0; s < sections.length; s += 1) {
|
|
80
|
+
nodes.push({
|
|
81
|
+
id: nextId("sec"),
|
|
82
|
+
key: buildKey(rel, "section", s + 1),
|
|
83
|
+
type: "section",
|
|
84
|
+
label: `${rel} section ${s + 1}`,
|
|
85
|
+
value: sections[s],
|
|
86
|
+
sectionItems: sections[s].split(/\s{2,}|,\s+/).filter(Boolean).slice(0, 8),
|
|
87
|
+
sourceRefs: [{ file: rel, original: sections[s] }]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
createdAt: new Date().toISOString(),
|
|
93
|
+
updatedAt: new Date().toISOString(),
|
|
94
|
+
nodes
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/cli/scanner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,IAAI,SAAS,GAAG,CAAC,CAAC;AAElB,SAAS,MAAM,CAAC,MAAc;IAC5B,SAAS,IAAI,CAAC,CAAC;IACf,OAAO,GAAG,MAAM,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,IAAY,EAAE,GAAW;IACvD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACjF,OAAO,GAAG,KAAK,IAAI,IAAI,IAAI,GAAG,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG;QACf,wHAAwH;QACxH,wCAAwC;KACzC,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,KAA6B,CAAC;QAClC,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAChD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACpD,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;gBACtB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,KAAe;IAC3D,MAAM,KAAK,GAAc,EAAE,CAAC;IAE5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACjC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAErC,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,MAAM,WAAW,GAAG,iCAAiC,CAAC;QACtD,IAAI,SAAiC,CAAC;QACtC,OAAO,CAAC,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACxD,MAAM,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9C,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,gCAAgC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7E,SAAS;YACX,CAAC;YACD,GAAG,IAAI,CAAC,CAAC;YACT,KAAK,CAAC,IAAI,CAAC;gBACT,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC;gBACjB,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC;gBAC/B,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,GAAG,GAAG,SAAS,GAAG,EAAE;gBAC3B,KAAK,EAAE,SAAS;gBAChB,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;aACpD,CAAC,CAAC;QACL,CAAC;QAED,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,MAAM,YAAY,GAAG,2BAA2B,CAAC;QACjD,IAAI,UAAkC,CAAC;QACvC,OAAO,CAAC,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAC1D,UAAU,IAAI,CAAC,CAAC;YAChB,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,GAAG,GAAG,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3D,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,SAAS;YACX,CAAC;YACD,MAAM,GAAG,GAAG,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3D,MAAM,IAAI,GAAG,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;YAC/C,KAAK,CAAC,IAAI,CAAC;gBACT,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC;gBACjB,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;gBACpC,IAAI;gBACJ,KAAK,EAAE,GAAG,GAAG,IAAI,IAAI,IAAI,UAAU,EAAE;gBACrC,KAAK,EAAE,GAAG;gBACV,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;gBACnB,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;aAC3C,CAAC,CAAC;QACL,CAAC;QAED,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC;gBACT,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC;gBACjB,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC;gBACpC,IAAI,EAAE,SAAS;gBACf,KAAK,EAAE,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,EAAE;gBAChC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;gBAClB,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC1E,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;aACnD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO;QACL,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,KAAK;KACN,CAAC;AACJ,CAAC"}
|