@mayson-org/inject-script 1.0.1 → 1.0.4
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/.env.example +9 -0
- package/README.md +66 -23
- package/bin/cli.mjs +1 -283
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +104 -0
- package/dist/core/find-root-layout.d.ts +2 -0
- package/dist/core/find-root-layout.d.ts.map +1 -0
- package/dist/core/find-root-layout.js +61 -0
- package/dist/core/generators.d.ts +4 -0
- package/dist/core/generators.d.ts.map +1 -0
- package/dist/core/generators.js +37 -0
- package/dist/core/injector.d.ts +4 -0
- package/dist/core/injector.d.ts.map +1 -0
- package/dist/core/injector.js +76 -0
- package/dist/core/types.d.ts +48 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/generators.d.ts.map +1 -1
- package/dist/generators.js +14 -27
- package/dist/index.d.ts +16 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -9
- package/dist/plugins/ascii/index.d.ts +4 -0
- package/dist/plugins/ascii/index.d.ts.map +1 -0
- package/dist/plugins/ascii/index.js +7 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +9 -0
- package/dist/plugins/metadata/index.d.ts +3 -0
- package/dist/plugins/metadata/index.d.ts.map +1 -0
- package/dist/plugins/metadata/index.js +14 -0
- package/dist/plugins/watermark/index.d.ts +4 -0
- package/dist/plugins/watermark/index.d.ts.map +1 -0
- package/dist/plugins/watermark/index.js +7 -0
- package/dist/plugins/watermark/template.d.ts +9 -0
- package/dist/plugins/watermark/template.d.ts.map +1 -0
- package/dist/plugins/watermark/template.js +128 -0
- package/dist/shared/ascii-art.d.ts +10 -0
- package/dist/shared/ascii-art.d.ts.map +1 -0
- package/dist/shared/ascii-art.js +44 -0
- package/dist/shared/config.d.ts +13 -0
- package/dist/shared/config.d.ts.map +1 -0
- package/dist/shared/config.js +25 -0
- package/dist/shared/generated-app-metadata.d.ts +28 -0
- package/dist/shared/generated-app-metadata.d.ts.map +1 -0
- package/dist/shared/generated-app-metadata.js +103 -0
- package/dist/shared/remix-url.d.ts +13 -0
- package/dist/shared/remix-url.d.ts.map +1 -0
- package/dist/shared/remix-url.js +16 -0
- package/package.json +14 -9
- package/src/find-root-layout.ts +0 -75
- package/src/generators.ts +0 -176
- package/src/index.ts +0 -42
- package/src/injector.ts +0 -108
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MaysonEnvConfig } from './config.js';
|
|
2
|
+
export type GeneratedAppMetadataValue = {
|
|
3
|
+
show_remix_pill?: boolean | null;
|
|
4
|
+
enable_remix?: boolean | null;
|
|
5
|
+
};
|
|
6
|
+
export type GeneratedAppMetadataResponse = {
|
|
7
|
+
response_code?: number;
|
|
8
|
+
response_type?: string;
|
|
9
|
+
message?: string;
|
|
10
|
+
value?: GeneratedAppMetadataValue;
|
|
11
|
+
};
|
|
12
|
+
export type FetchGeneratedAppMetadataOptions = {
|
|
13
|
+
webToken?: string;
|
|
14
|
+
fetchImpl?: typeof fetch;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
};
|
|
17
|
+
/** When metadata record does not exist yet — show pill and allow remix. */
|
|
18
|
+
export declare const METADATA_NOT_FOUND_DEFAULTS: Pick<MaysonEnvConfig, 'showRemixPill' | 'enableRemix'>;
|
|
19
|
+
export declare function resolveGeneratedAppMetadataUrl(config: Pick<MaysonEnvConfig, 'apiBaseUrl' | 'workspaceId' | 'collectionId'>): string | null;
|
|
20
|
+
export declare function shouldFetchGeneratedAppMetadata(config: MaysonEnvConfig, webToken?: string): boolean;
|
|
21
|
+
export declare function mapGeneratedAppMetadataValue(value: GeneratedAppMetadataValue): Partial<Pick<MaysonEnvConfig, 'showRemixPill' | 'enableRemix'>>;
|
|
22
|
+
/** True when API indicates no metadata row exists yet (HTTP or body 404). */
|
|
23
|
+
export declare function isGeneratedAppMetadataNotFound(httpStatus: number, data: GeneratedAppMetadataResponse | null | undefined): boolean;
|
|
24
|
+
/** Fetch generated-app-metadata. Returns null on hard failure (network / non-404 errors). */
|
|
25
|
+
export declare function fetchGeneratedAppMetadata(config: MaysonEnvConfig, options?: FetchGeneratedAppMetadataOptions): Promise<Partial<Pick<MaysonEnvConfig, 'showRemixPill' | 'enableRemix'>> | null>;
|
|
26
|
+
/** Local env config merged with generated-app-metadata when credentials are set. */
|
|
27
|
+
export declare function resolveMaysonConfig(options?: FetchGeneratedAppMetadataOptions): Promise<MaysonEnvConfig>;
|
|
28
|
+
//# sourceMappingURL=generated-app-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generated-app-metadata.d.ts","sourceRoot":"","sources":["../../src/shared/generated-app-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,MAAM,MAAM,yBAAyB,GAAG;IACtC,eAAe,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,yBAAyB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B,EAAE,IAAI,CAC5C,eAAe,EACf,eAAe,GAAG,aAAa,CAIhC,CAAC;AAEF,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,YAAY,GAAG,aAAa,GAAG,cAAc,CAAC,GAC3E,MAAM,GAAG,IAAI,CASf;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,eAAe,EACvB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAGT;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC,CAWjE;AAED,6EAA6E;AAC7E,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACpD,OAAO,CAiBT;AAYD,6FAA6F;AAC7F,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,gCAAqC,GAC7C,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC,CAiDjF;AAED,oFAAoF;AACpF,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,OAAO,CAAC,eAAe,CAAC,CAa1B"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { loadMaysonEnvConfig } from './config.js';
|
|
2
|
+
/** When metadata record does not exist yet — show pill and allow remix. */
|
|
3
|
+
export const METADATA_NOT_FOUND_DEFAULTS = {
|
|
4
|
+
showRemixPill: true,
|
|
5
|
+
enableRemix: true,
|
|
6
|
+
};
|
|
7
|
+
export function resolveGeneratedAppMetadataUrl(config) {
|
|
8
|
+
const base = config.apiBaseUrl.replace(/\/$/, '');
|
|
9
|
+
const workspaceId = config.workspaceId.trim();
|
|
10
|
+
const collectionId = config.collectionId.trim();
|
|
11
|
+
if (!base || !workspaceId || !collectionId) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return `${base}/sigma/web/v1/generated-app-metadata/workspaces/${encodeURIComponent(workspaceId)}/collections/${encodeURIComponent(collectionId)}`;
|
|
15
|
+
}
|
|
16
|
+
export function shouldFetchGeneratedAppMetadata(config, webToken) {
|
|
17
|
+
const token = (webToken ?? config.webToken).trim();
|
|
18
|
+
return resolveGeneratedAppMetadataUrl(config) !== null && token.length > 0;
|
|
19
|
+
}
|
|
20
|
+
export function mapGeneratedAppMetadataValue(value) {
|
|
21
|
+
const partial = {};
|
|
22
|
+
if (value.show_remix_pill !== null && value.show_remix_pill !== undefined) {
|
|
23
|
+
partial.showRemixPill = value.show_remix_pill;
|
|
24
|
+
}
|
|
25
|
+
if (value.enable_remix !== null && value.enable_remix !== undefined) {
|
|
26
|
+
partial.enableRemix = value.enable_remix;
|
|
27
|
+
}
|
|
28
|
+
return partial;
|
|
29
|
+
}
|
|
30
|
+
/** True when API indicates no metadata row exists yet (HTTP or body 404). */
|
|
31
|
+
export function isGeneratedAppMetadataNotFound(httpStatus, data) {
|
|
32
|
+
if (httpStatus === 404) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (!data || typeof data !== 'object') {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (data.response_code === 404) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (data.response_type === 'failed' &&
|
|
42
|
+
/generated app metadata not found/i.test(data.message ?? '')) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
async function readJsonSafe(response) {
|
|
48
|
+
try {
|
|
49
|
+
return (await response.json());
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Fetch generated-app-metadata. Returns null on hard failure (network / non-404 errors). */
|
|
56
|
+
export async function fetchGeneratedAppMetadata(config, options = {}) {
|
|
57
|
+
const url = resolveGeneratedAppMetadataUrl(config);
|
|
58
|
+
const token = (options.webToken ?? config.webToken).trim();
|
|
59
|
+
if (!url || !token) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
63
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetchImpl(url, {
|
|
66
|
+
headers: {
|
|
67
|
+
Accept: 'application/json',
|
|
68
|
+
'M-Web-Token': token,
|
|
69
|
+
},
|
|
70
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
71
|
+
});
|
|
72
|
+
const data = await readJsonSafe(response);
|
|
73
|
+
if (isGeneratedAppMetadataNotFound(response.status, data)) {
|
|
74
|
+
console.log('[mayson] generated-app-metadata not found — defaulting show_remix_pill=true, enable_remix=true');
|
|
75
|
+
return { ...METADATA_NOT_FOUND_DEFAULTS };
|
|
76
|
+
}
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
console.warn(`[mayson] generated-app-metadata request failed (${response.status}) — using local config`);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
if (!data?.value || typeof data.value !== 'object') {
|
|
82
|
+
console.warn('[mayson] generated-app-metadata response invalid — using local config');
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return mapGeneratedAppMetadataValue(data.value);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.warn(`[mayson] generated-app-metadata fetch failed: ${err.message} — using local config`);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Local env config merged with generated-app-metadata when credentials are set. */
|
|
93
|
+
export async function resolveMaysonConfig(options = {}) {
|
|
94
|
+
const local = loadMaysonEnvConfig();
|
|
95
|
+
if (!shouldFetchGeneratedAppMetadata(local, options.webToken)) {
|
|
96
|
+
return local;
|
|
97
|
+
}
|
|
98
|
+
const api = await fetchGeneratedAppMetadata(local, options);
|
|
99
|
+
if (!api) {
|
|
100
|
+
return local;
|
|
101
|
+
}
|
|
102
|
+
return { ...local, ...api };
|
|
103
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** UTM source for remix links opened from the watermark pill. */
|
|
2
|
+
export declare const BADGE_UTM_SOURCE = "mayson-badge";
|
|
3
|
+
export type RemixUrlConfig = {
|
|
4
|
+
appBaseUrl?: string;
|
|
5
|
+
collectionId?: string;
|
|
6
|
+
enableRemix?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Build the mayson.dev remix redirect URL (same pattern as the legacy build plugins).
|
|
10
|
+
* Baked into the generated watermark component at inject time.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildRemixRedirectUrl(config?: RemixUrlConfig): string;
|
|
13
|
+
//# sourceMappingURL=remix-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remix-url.d.ts","sourceRoot":"","sources":["../../src/shared/remix-url.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,iBAAiB,CAAC;AAE/C,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,GAAE,cAAmB,GAAG,MAAM,CAWzE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** UTM source for remix links opened from the watermark pill. */
|
|
2
|
+
export const BADGE_UTM_SOURCE = 'mayson-badge';
|
|
3
|
+
/**
|
|
4
|
+
* Build the mayson.dev remix redirect URL (same pattern as the legacy build plugins).
|
|
5
|
+
* Baked into the generated watermark component at inject time.
|
|
6
|
+
*/
|
|
7
|
+
export function buildRemixRedirectUrl(config = {}) {
|
|
8
|
+
const base = (config.appBaseUrl ?? 'https://mayson.dev').replace(/\/$/, '') || 'https://mayson.dev';
|
|
9
|
+
const collectionId = config.collectionId?.trim() ?? '';
|
|
10
|
+
const enableRemix = config.enableRemix === true;
|
|
11
|
+
if (enableRemix && collectionId) {
|
|
12
|
+
const remixPath = `/workspaces?coll=${collectionId}&utm_source=${BADGE_UTM_SOURCE}&action=remix`;
|
|
13
|
+
return `${base}/redirect?to=${encodeURIComponent(remixPath)}`;
|
|
14
|
+
}
|
|
15
|
+
return `${base}/`;
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mayson-org/inject-script",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "CLI
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "CLI and library to locate Next.js App Router root layouts, generate Mayson plugin components, and inject them into the layout tree.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
|
-
"module": "./dist/index.mjs",
|
|
7
6
|
"types": "./dist/index.d.ts",
|
|
8
7
|
"type": "module",
|
|
9
8
|
"bin": {
|
|
@@ -12,7 +11,7 @@
|
|
|
12
11
|
"exports": {
|
|
13
12
|
".": {
|
|
14
13
|
"types": "./dist/index.d.ts",
|
|
15
|
-
"import": "./dist/index.
|
|
14
|
+
"import": "./dist/index.js",
|
|
16
15
|
"default": "./dist/index.js"
|
|
17
16
|
}
|
|
18
17
|
},
|
|
@@ -25,8 +24,8 @@
|
|
|
25
24
|
"files": [
|
|
26
25
|
"bin",
|
|
27
26
|
"dist",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
27
|
+
"README.md",
|
|
28
|
+
".env.example"
|
|
30
29
|
],
|
|
31
30
|
"publishConfig": {
|
|
32
31
|
"access": "public"
|
|
@@ -35,12 +34,18 @@
|
|
|
35
34
|
"nextjs",
|
|
36
35
|
"app-router",
|
|
37
36
|
"layout",
|
|
38
|
-
"badge",
|
|
39
37
|
"watermark",
|
|
40
38
|
"metadata",
|
|
41
39
|
"mayson",
|
|
42
40
|
"cli"
|
|
43
41
|
],
|
|
44
42
|
"author": "",
|
|
45
|
-
"license": "MIT"
|
|
46
|
-
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^26.6.1",
|
|
49
|
+
"typescript": "^5.9.3"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/find-root-layout.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
const LAYOUT_EXTENSIONS = ['.tsx', '.jsx', '.ts', '.js'];
|
|
5
|
-
|
|
6
|
-
function findLayoutFiles(dir: string, fileList: string[] = []): string[] {
|
|
7
|
-
if (!fs.existsSync(dir)) return fileList;
|
|
8
|
-
|
|
9
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
10
|
-
|
|
11
|
-
for (const entry of entries) {
|
|
12
|
-
const fullPath = path.join(dir, entry.name);
|
|
13
|
-
|
|
14
|
-
if (entry.isDirectory()) {
|
|
15
|
-
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
findLayoutFiles(fullPath, fileList);
|
|
19
|
-
} else if (entry.isFile()) {
|
|
20
|
-
const ext = path.extname(entry.name);
|
|
21
|
-
const base = path.basename(entry.name, ext);
|
|
22
|
-
if (base === 'layout' && LAYOUT_EXTENSIONS.includes(ext)) {
|
|
23
|
-
fileList.push(fullPath);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return fileList;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function scoreLayoutCandidate(filePath: string, projectRoot: string): number {
|
|
32
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
33
|
-
const relativePath = path.relative(projectRoot, filePath);
|
|
34
|
-
const depth = relativePath.split(path.sep).length;
|
|
35
|
-
|
|
36
|
-
let score = 100 - depth;
|
|
37
|
-
|
|
38
|
-
if (/<html/i.test(content)) score += 50;
|
|
39
|
-
if (/<body/i.test(content)) score += 50;
|
|
40
|
-
|
|
41
|
-
const isDirectAppChild =
|
|
42
|
-
/^src[/\\]app[/\\]layout\.[jt]sx?$/.test(relativePath) ||
|
|
43
|
-
/^app[/\\]layout\.[jt]sx?$/.test(relativePath);
|
|
44
|
-
if (isDirectAppChild) score += 40;
|
|
45
|
-
|
|
46
|
-
return score;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function findRootLayout(projectRoot: string = process.cwd()): string | null {
|
|
50
|
-
const possibleAppDirs = [
|
|
51
|
-
path.join(projectRoot, 'src', 'app'),
|
|
52
|
-
path.join(projectRoot, 'app'),
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const candidateFiles: string[] = [];
|
|
56
|
-
|
|
57
|
-
for (const appDir of possibleAppDirs) {
|
|
58
|
-
if (fs.existsSync(appDir)) {
|
|
59
|
-
findLayoutFiles(appDir, candidateFiles);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (candidateFiles.length === 0) {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const scored = candidateFiles.map((filePath) => ({
|
|
68
|
-
filePath,
|
|
69
|
-
score: scoreLayoutCandidate(filePath, projectRoot),
|
|
70
|
-
}));
|
|
71
|
-
|
|
72
|
-
scored.sort((a, b) => b.score - a.score);
|
|
73
|
-
|
|
74
|
-
return scored[0]?.filePath || null;
|
|
75
|
-
}
|
package/src/generators.ts
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
export type ComponentType = 'badge' | 'watermark' | 'ascii' | 'metadata' | 'all';
|
|
5
|
-
|
|
6
|
-
export interface ComponentGeneratorOptions {
|
|
7
|
-
outputDir: string;
|
|
8
|
-
isTypeScript?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface GeneratedComponentInfo {
|
|
12
|
-
name: string;
|
|
13
|
-
filePath: string;
|
|
14
|
-
importStatement: string;
|
|
15
|
-
jsxTag: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const BADGE_TEMPLATE = `'use client';
|
|
19
|
-
import React from 'react';
|
|
20
|
-
|
|
21
|
-
export function MaysonBadge() {
|
|
22
|
-
return (
|
|
23
|
-
<div
|
|
24
|
-
style={{
|
|
25
|
-
position: 'fixed',
|
|
26
|
-
bottom: '16px',
|
|
27
|
-
right: '16px',
|
|
28
|
-
zIndex: 99999,
|
|
29
|
-
display: 'flex',
|
|
30
|
-
alignItems: 'center',
|
|
31
|
-
gap: '8px',
|
|
32
|
-
padding: '6px 12px',
|
|
33
|
-
backgroundColor: 'rgba(18, 18, 20, 0.9)',
|
|
34
|
-
color: '#ffffff',
|
|
35
|
-
borderRadius: '9999px',
|
|
36
|
-
fontSize: '12px',
|
|
37
|
-
fontWeight: 500,
|
|
38
|
-
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
39
|
-
border: '1px solid rgba(255, 255, 255, 0.15)',
|
|
40
|
-
boxShadow: '0 4px 14px rgba(0, 0, 0, 0.25)',
|
|
41
|
-
backdropFilter: 'blur(8px)',
|
|
42
|
-
cursor: 'pointer',
|
|
43
|
-
userSelect: 'none',
|
|
44
|
-
transition: 'transform 0.2s ease',
|
|
45
|
-
}}
|
|
46
|
-
>
|
|
47
|
-
<span style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#6366f1' }} />
|
|
48
|
-
<span>Built with <strong>Mayson</strong></span>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
const WATERMARK_TEMPLATE = `'use client';
|
|
55
|
-
import React from 'react';
|
|
56
|
-
|
|
57
|
-
export function MaysonWatermark() {
|
|
58
|
-
return (
|
|
59
|
-
<div
|
|
60
|
-
style={{
|
|
61
|
-
position: 'fixed',
|
|
62
|
-
bottom: '12px',
|
|
63
|
-
left: '12px',
|
|
64
|
-
zIndex: 99999,
|
|
65
|
-
opacity: 0.4,
|
|
66
|
-
fontSize: '11px',
|
|
67
|
-
fontFamily: 'monospace',
|
|
68
|
-
color: '#888888',
|
|
69
|
-
pointerEvents: 'none',
|
|
70
|
-
userSelect: 'none',
|
|
71
|
-
}}
|
|
72
|
-
>
|
|
73
|
-
⚡ Mayson Watermark
|
|
74
|
-
</div>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
`;
|
|
78
|
-
|
|
79
|
-
const ASCII_TEMPLATE = `'use client';
|
|
80
|
-
import React from 'react';
|
|
81
|
-
|
|
82
|
-
/*
|
|
83
|
-
███╗ ███╗██xA1██╗ ██╗███████╗██████╗ ██████╗
|
|
84
|
-
████╗ ████║██║██║ ██║██╔════╝██╔═══██╗██╔══██╗
|
|
85
|
-
██╔████╔██║██║██║ ██║███████╗██║ ██║██║ ██║
|
|
86
|
-
██║╚██╔╝██║██║██║ ██║╚════██║██║ ██║██║ ██║
|
|
87
|
-
██║ ╚═╝ ██║██║╚██████╔╝███████║╚██████╔╝██████╔╝
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
export function MaysonAscii() {
|
|
91
|
-
return (
|
|
92
|
-
<div
|
|
93
|
-
style={{
|
|
94
|
-
position: 'fixed',
|
|
95
|
-
top: '8px',
|
|
96
|
-
right: '12px',
|
|
97
|
-
zIndex: 99999,
|
|
98
|
-
fontSize: '10px',
|
|
99
|
-
fontFamily: 'monospace',
|
|
100
|
-
color: 'rgba(255, 255, 255, 0.6)',
|
|
101
|
-
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
|
102
|
-
padding: '4px 8px',
|
|
103
|
-
borderRadius: '4px',
|
|
104
|
-
pointerEvents: 'none',
|
|
105
|
-
}}
|
|
106
|
-
>
|
|
107
|
-
MAYSON :: RUNTIME ACTIVE
|
|
108
|
-
</div>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
`;
|
|
112
|
-
|
|
113
|
-
const METADATA_TEMPLATE = `'use client';
|
|
114
|
-
import React from 'react';
|
|
115
|
-
|
|
116
|
-
export function MaysonMetadata() {
|
|
117
|
-
return (
|
|
118
|
-
<meta name="mayson-generator" content="Mayson App Platform" />
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
`;
|
|
122
|
-
|
|
123
|
-
export function generateComponents(
|
|
124
|
-
types: ComponentType[] = ['badge'],
|
|
125
|
-
options: ComponentGeneratorOptions
|
|
126
|
-
): GeneratedComponentInfo[] {
|
|
127
|
-
const { outputDir, isTypeScript = true } = options;
|
|
128
|
-
const ext = isTypeScript ? '.tsx' : '.jsx';
|
|
129
|
-
const results: GeneratedComponentInfo[] = [];
|
|
130
|
-
|
|
131
|
-
if (!fs.existsSync(outputDir)) {
|
|
132
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const selectedTypes = types.includes('all')
|
|
136
|
-
? (['badge', 'watermark', 'ascii', 'metadata'] as ComponentType[])
|
|
137
|
-
: types;
|
|
138
|
-
|
|
139
|
-
for (const type of selectedTypes) {
|
|
140
|
-
let name = '';
|
|
141
|
-
let code = '';
|
|
142
|
-
|
|
143
|
-
switch (type) {
|
|
144
|
-
case 'badge':
|
|
145
|
-
name = 'MaysonBadge';
|
|
146
|
-
code = BADGE_TEMPLATE;
|
|
147
|
-
break;
|
|
148
|
-
case 'watermark':
|
|
149
|
-
name = 'MaysonWatermark';
|
|
150
|
-
code = WATERMARK_TEMPLATE;
|
|
151
|
-
break;
|
|
152
|
-
case 'ascii':
|
|
153
|
-
name = 'MaysonAscii';
|
|
154
|
-
code = ASCII_TEMPLATE;
|
|
155
|
-
break;
|
|
156
|
-
case 'metadata':
|
|
157
|
-
name = 'MaysonMetadata';
|
|
158
|
-
code = METADATA_TEMPLATE;
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (name && code) {
|
|
163
|
-
const filePath = path.join(outputDir, `${name}${ext}`);
|
|
164
|
-
fs.writeFileSync(filePath, code, 'utf-8');
|
|
165
|
-
|
|
166
|
-
results.push({
|
|
167
|
-
name,
|
|
168
|
-
filePath,
|
|
169
|
-
importStatement: `import { ${name} } from './${name}';`,
|
|
170
|
-
jsxTag: `<${name} />`,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return results;
|
|
176
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { findRootLayout } from './find-root-layout.js';
|
|
3
|
-
import { generateComponents, ComponentType, GeneratedComponentInfo } from './generators.js';
|
|
4
|
-
import { injectComponentsIntoLayout, InjectorOptions, InjectorResult } from './injector.js';
|
|
5
|
-
|
|
6
|
-
export { findRootLayout, generateComponents, injectComponentsIntoLayout };
|
|
7
|
-
export type { ComponentType, GeneratedComponentInfo, InjectorOptions, InjectorResult };
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Convenience function to auto-detect Next.js App Router root layout, generate component files, and inject them.
|
|
11
|
-
*/
|
|
12
|
-
export function runAutoInject(
|
|
13
|
-
projectRoot: string = process.cwd(),
|
|
14
|
-
types: ComponentType[] = ['badge'],
|
|
15
|
-
dryRun: boolean = false
|
|
16
|
-
): InjectorResult {
|
|
17
|
-
const layoutPath = findRootLayout(projectRoot);
|
|
18
|
-
|
|
19
|
-
if (!layoutPath) {
|
|
20
|
-
return {
|
|
21
|
-
success: false,
|
|
22
|
-
filePath: '',
|
|
23
|
-
injectedComponents: [],
|
|
24
|
-
skippedComponents: [],
|
|
25
|
-
error: `Could not locate Next.js App Router root layout in ${projectRoot}`,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const isTypeScript = layoutPath.endsWith('.tsx') || layoutPath.endsWith('.ts');
|
|
30
|
-
const targetDir = path.dirname(layoutPath);
|
|
31
|
-
|
|
32
|
-
const generatedComponents = generateComponents(types, {
|
|
33
|
-
outputDir: targetDir,
|
|
34
|
-
isTypeScript,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
return injectComponentsIntoLayout({
|
|
38
|
-
filePath: layoutPath,
|
|
39
|
-
components: generatedComponents,
|
|
40
|
-
dryRun,
|
|
41
|
-
});
|
|
42
|
-
}
|
package/src/injector.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { GeneratedComponentInfo } from './generators.js';
|
|
4
|
-
|
|
5
|
-
export interface InjectorOptions {
|
|
6
|
-
filePath: string;
|
|
7
|
-
components: GeneratedComponentInfo[];
|
|
8
|
-
dryRun?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface InjectorResult {
|
|
12
|
-
success: boolean;
|
|
13
|
-
filePath: string;
|
|
14
|
-
injectedComponents: string[];
|
|
15
|
-
skippedComponents: string[];
|
|
16
|
-
modifiedContent?: string;
|
|
17
|
-
error?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function injectComponentsIntoLayout(options: InjectorOptions): InjectorResult {
|
|
21
|
-
const { filePath, components, dryRun = false } = options;
|
|
22
|
-
|
|
23
|
-
if (!fs.existsSync(filePath)) {
|
|
24
|
-
return {
|
|
25
|
-
success: false,
|
|
26
|
-
filePath,
|
|
27
|
-
injectedComponents: [],
|
|
28
|
-
skippedComponents: [],
|
|
29
|
-
error: `Layout file not found: ${filePath}`,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let content = fs.readFileSync(filePath, 'utf-8');
|
|
34
|
-
const injectedComponents: string[] = [];
|
|
35
|
-
const skippedComponents: string[] = [];
|
|
36
|
-
|
|
37
|
-
for (const comp of components) {
|
|
38
|
-
const importMarker = comp.name;
|
|
39
|
-
const jsxMarker = `<${comp.name}`;
|
|
40
|
-
|
|
41
|
-
// Skip if already injected
|
|
42
|
-
if (content.includes(importMarker) || content.includes(jsxMarker)) {
|
|
43
|
-
skippedComponents.push(comp.name);
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Determine component file import path relative to layout.tsx
|
|
48
|
-
const layoutDir = path.dirname(filePath);
|
|
49
|
-
const compDir = path.dirname(comp.filePath);
|
|
50
|
-
const compBaseName = path.basename(comp.filePath, path.extname(comp.filePath));
|
|
51
|
-
|
|
52
|
-
let relativeImportPath = path.relative(layoutDir, path.join(compDir, compBaseName));
|
|
53
|
-
if (!relativeImportPath.startsWith('.')) {
|
|
54
|
-
relativeImportPath = `./${relativeImportPath}`;
|
|
55
|
-
}
|
|
56
|
-
// Convert backslashes for Windows path consistency
|
|
57
|
-
relativeImportPath = relativeImportPath.replace(/\\/g, '/');
|
|
58
|
-
|
|
59
|
-
const importLine = `import { ${comp.name} } from '${relativeImportPath}';\n`;
|
|
60
|
-
|
|
61
|
-
// 1. Insert import statement
|
|
62
|
-
if (/import\s+.*?from\s+['"].*?['"];?/g.test(content)) {
|
|
63
|
-
// Insert after the last import statement
|
|
64
|
-
const importMatches = Array.from(content.matchAll(/import\s+.*?from\s+['"].*?['"];?/g));
|
|
65
|
-
const lastMatch = importMatches[importMatches.length - 1];
|
|
66
|
-
if (lastMatch && lastMatch.index !== undefined) {
|
|
67
|
-
const insertPos = lastMatch.index + lastMatch[0].length;
|
|
68
|
-
content = content.slice(0, insertPos) + '\n' + importLine + content.slice(insertPos);
|
|
69
|
-
} else {
|
|
70
|
-
content = importLine + content;
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
content = importLine + content;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// 2. Insert JSX tag into return block
|
|
77
|
-
const jsxSnippet = `\n {/* @mayson-component-injected: ${comp.name} */}\n ${comp.jsxTag}`;
|
|
78
|
-
|
|
79
|
-
if (/<\/body>/i.test(content)) {
|
|
80
|
-
content = content.replace(/<\/body>/i, `${jsxSnippet}\n </body>`);
|
|
81
|
-
} else if (/<\/html>/i.test(content)) {
|
|
82
|
-
content = content.replace(/<\/html>/i, `${jsxSnippet}\n </html>`);
|
|
83
|
-
} else {
|
|
84
|
-
const lastClosingIndex = content.lastIndexOf('</');
|
|
85
|
-
if (lastClosingIndex !== -1) {
|
|
86
|
-
content =
|
|
87
|
-
content.slice(0, lastClosingIndex) +
|
|
88
|
-
jsxSnippet +
|
|
89
|
-
'\n ' +
|
|
90
|
-
content.slice(lastClosingIndex);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
injectedComponents.push(comp.name);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (injectedComponents.length > 0 && !dryRun) {
|
|
98
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
success: true,
|
|
103
|
-
filePath,
|
|
104
|
-
injectedComponents,
|
|
105
|
-
skippedComponents,
|
|
106
|
-
modifiedContent: content,
|
|
107
|
-
};
|
|
108
|
-
}
|