@mayson-org/inject-script 1.0.0 → 1.0.3

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.
Files changed (58) hide show
  1. package/.env.example +9 -0
  2. package/README.md +57 -45
  3. package/bin/cli.mjs +1 -158
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +104 -0
  7. package/dist/core/find-root-layout.d.ts +2 -0
  8. package/dist/core/find-root-layout.d.ts.map +1 -0
  9. package/dist/core/find-root-layout.js +61 -0
  10. package/dist/core/generators.d.ts +4 -0
  11. package/dist/core/generators.d.ts.map +1 -0
  12. package/dist/core/generators.js +37 -0
  13. package/dist/core/injector.d.ts +4 -0
  14. package/dist/core/injector.d.ts.map +1 -0
  15. package/dist/core/injector.js +76 -0
  16. package/dist/core/types.d.ts +48 -0
  17. package/dist/core/types.d.ts.map +1 -0
  18. package/dist/core/types.js +1 -0
  19. package/dist/find-root-layout.d.ts +0 -3
  20. package/dist/find-root-layout.d.ts.map +1 -1
  21. package/dist/find-root-layout.js +1 -15
  22. package/dist/generators.d.ts +13 -0
  23. package/dist/generators.d.ts.map +1 -0
  24. package/dist/generators.js +134 -0
  25. package/dist/index.d.ts +14 -6
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +65 -9
  28. package/dist/injector.d.ts +5 -3
  29. package/dist/injector.d.ts.map +1 -1
  30. package/dist/injector.js +67 -61
  31. package/dist/plugins/ascii/index.d.ts +3 -0
  32. package/dist/plugins/ascii/index.d.ts.map +1 -0
  33. package/dist/plugins/ascii/index.js +25 -0
  34. package/dist/plugins/index.d.ts +7 -0
  35. package/dist/plugins/index.d.ts.map +1 -0
  36. package/dist/plugins/index.js +9 -0
  37. package/dist/plugins/metadata/index.d.ts +3 -0
  38. package/dist/plugins/metadata/index.d.ts.map +1 -0
  39. package/dist/plugins/metadata/index.js +14 -0
  40. package/dist/plugins/watermark/index.d.ts +4 -0
  41. package/dist/plugins/watermark/index.d.ts.map +1 -0
  42. package/dist/plugins/watermark/index.js +7 -0
  43. package/dist/plugins/watermark/template.d.ts +9 -0
  44. package/dist/plugins/watermark/template.d.ts.map +1 -0
  45. package/dist/plugins/watermark/template.js +128 -0
  46. package/dist/shared/config.d.ts +13 -0
  47. package/dist/shared/config.d.ts.map +1 -0
  48. package/dist/shared/config.js +25 -0
  49. package/dist/shared/generated-app-metadata.d.ts +28 -0
  50. package/dist/shared/generated-app-metadata.d.ts.map +1 -0
  51. package/dist/shared/generated-app-metadata.js +103 -0
  52. package/dist/shared/remix-url.d.ts +13 -0
  53. package/dist/shared/remix-url.d.ts.map +1 -0
  54. package/dist/shared/remix-url.js +16 -0
  55. package/package.json +15 -8
  56. package/src/find-root-layout.ts +0 -88
  57. package/src/index.ts +0 -27
  58. package/src/injector.ts +0 -101
package/.env.example ADDED
@@ -0,0 +1,9 @@
1
+ # Set before running inject-script (e.g. in CI / Vercel build env)
2
+
3
+ MAYSON_WORKSPACE_ID=
4
+ MAYSON_COLLECTION_ID=
5
+ MAYSON_API_BASE_URL=
6
+ MAYSON_APP_BASE_URL=https://mayson.dev
7
+
8
+ # Auth for generated-app-metadata (show_remix_pill / enable_remix)
9
+ MAYSON_WEB_TOKEN=
package/README.md CHANGED
@@ -1,71 +1,83 @@
1
- # @mayson/inject-script
1
+ # @mayson-org/inject-script
2
2
 
3
- A lightweight CLI tool and Node.js package designed for **Next.js App Router** applications. It dynamically locates the project's root layout file (regardless of whether it's stored in `app/layout.tsx`, `src/app/layout.tsx`, or inside route groups like `app/(main)/layout.js`) and injects a floating `<iframe>` pointing to `https://mayson.dev/`.
3
+ Locate the Next.js App Router root layout, generate Mayson plugin components next to it, and inject imports + JSX into the layout. Injection logic is unchanged; plugins supply component source.
4
4
 
5
- ---
5
+ Requires **Node 20+**.
6
6
 
7
- ## Quick Usage via `npx`
8
-
9
- Run directly in any Next.js project root:
7
+ ## Install
10
8
 
11
9
  ```bash
12
- npx @mayson/inject-script
10
+ bun add -d @mayson-org/inject-script@latest
11
+ # or
12
+ npx @mayson-org/inject-script --type=watermark
13
13
  ```
14
14
 
15
- Or pass optional flags:
15
+ ## Quick usage
16
16
 
17
17
  ```bash
18
- # Specify custom iframe URL
19
- npx @mayson/inject-script --url=https://mayson.dev/my-app
18
+ npx @mayson-org/inject-script --type=watermark
19
+ npx @mayson-org/inject-script --type=all
20
+ npx @mayson-org/inject-script --type=watermark --dry-run
21
+ npx @mayson-org/inject-script --dir=./my-app --type=watermark
22
+ ```
20
23
 
21
- # Dry-run mode (preview changes without modifying files)
22
- npx @mayson/inject-script --dry-run
24
+ ## Watermark
23
25
 
24
- # Specify target project directory
25
- npx @mayson/inject-script --dir=./my-nextjs-app
26
- ```
26
+ `--type=watermark` writes `MaysonWatermark.tsx` beside the root layout and injects `<MaysonWatermark />` before `</body>`.
27
27
 
28
- ---
28
+ The pill:
29
29
 
30
- ## Adding to Next.js `package.json` Build Scripts
30
+ - Fixed bottom-right “Edit with Mayson” (logo + label)
31
+ - Dismiss (×) hides it for the session
32
+ - Click opens the Mayson remix URL (baked at inject time)
31
33
 
32
- You can automate this script during deployment builds (e.g. on Vercel, Netlify, Docker, or GitHub Actions) by adding it to `package.json`:
34
+ ### API gating (`show_remix_pill` / `enable_remix`)
33
35
 
34
- ```json
35
- {
36
- "scripts": {
37
- "dev": "next dev",
38
- "prebuild": "npx @mayson/inject-script",
39
- "build": "next build"
40
- }
41
- }
42
- ```
36
+ At inject time the CLI fetches **generated-app-metadata** (same API as the old build plugins):
43
37
 
44
- ---
38
+ `GET {MAYSON_API_BASE_URL}/sigma/web/v1/generated-app-metadata/workspaces/{id}/collections/{id}`
39
+ Header: `M-Web-Token: {MAYSON_WEB_TOKEN}`
45
40
 
46
- ## Programmatic API Usage
41
+ | API field | Effect |
42
+ |-----------|--------|
43
+ | `show_remix_pill` | If `false`, watermark is **not** injected; if `true`, inject |
44
+ | `enable_remix` | Baked into the component remix URL (`true` → collection remix redirect; otherwise app home) |
47
45
 
48
- You can also import and use `inject-script` in custom build scripts:
46
+ **Metadata not found (404):** if the API returns HTTP 404 or a body like `{ "response_code": 404, "message": "Generated app metadata not found", "value": {} }`, treat as **`show_remix_pill=true`** and **`enable_remix=true`** (show watermark, remixable).
49
47
 
50
- ```javascript
51
- import { runAutoInject, findRootLayout, injectMaysonIframe } from 'inject-script';
48
+ Defaults when the API is not called or fails for other reasons: `showRemixPill=false`, `enableRemix=false` (watermark skipped).
52
49
 
53
- // 1. Auto-detect and inject in one call
54
- const result = runAutoInject(process.cwd(), 'https://mayson.dev/');
50
+ ### Env vars (inject-time)
55
51
 
56
- if (result.success) {
57
- console.log(`Injected to: ${result.filePath}`);
58
- }
52
+ | Variable | Purpose |
53
+ |----------|---------|
54
+ | `MAYSON_WORKSPACE_ID` | Workspace id |
55
+ | `MAYSON_COLLECTION_ID` | Collection id (also baked into remix URL) |
56
+ | `MAYSON_API_BASE_URL` | API base for metadata |
57
+ | `MAYSON_APP_BASE_URL` | App base (default `https://mayson.dev`) |
58
+ | `MAYSON_WEB_TOKEN` | Auth for generated-app-metadata |
59
59
 
60
- // 2. Or manually find layout and inspect
61
- const rootLayoutPath = findRootLayout('./my-project');
62
- console.log('Root Layout found at:', rootLayoutPath);
60
+ ## Programmatic API
61
+
62
+ ```ts
63
+ import { runAutoInject } from '@mayson-org/inject-script';
64
+
65
+ const result = await runAutoInject(process.cwd(), ['watermark']);
63
66
  ```
64
67
 
65
- ---
68
+ ## Package layout
69
+
70
+ ```text
71
+ src/
72
+ cli.ts / index.ts
73
+ core/ # find-root-layout, injector, generators
74
+ plugins/watermark/ # remix pill template
75
+ plugins/ascii|metadata # stubs for later restores
76
+ shared/ # config, generated-app-metadata, remix-url
77
+ ```
66
78
 
67
- ## Features
79
+ ## Notes
68
80
 
69
- - 🔍 **Dynamic Root Layout Resolution**: Scans `app/`, `src/app/`, and nested route groups for layout files and scores candidates based on HTML/Body root tags.
70
- - ⚡ **Idempotent**: Safe to run multiple times without creating duplicate iframe injections.
71
- - 🛡️ **Zero Runtime Overhead**: Inserts a self-contained React `<iframe />` directly into the JSX tree.
81
+ - Default `--type` is `watermark`.
82
+ - Idempotent: skips components already present in the layout.
83
+ - Does not patch `.next` HTML; source inject only.
package/bin/cli.mjs CHANGED
@@ -1,159 +1,2 @@
1
1
  #!/usr/bin/env node
2
-
3
- import fs from 'fs';
4
- import path from 'path';
5
-
6
- function findLayoutFiles(dir, fileList = []) {
7
- if (!fs.existsSync(dir)) return fileList;
8
- const entries = fs.readdirSync(dir, { withFileTypes: true });
9
-
10
- for (const entry of entries) {
11
- const fullPath = path.join(dir, entry.name);
12
- if (entry.isDirectory()) {
13
- if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
14
- findLayoutFiles(fullPath, fileList);
15
- } else if (entry.isFile()) {
16
- const ext = path.extname(entry.name);
17
- const base = path.basename(entry.name, ext);
18
- if (base === 'layout' && ['.tsx', '.jsx', '.ts', '.js'].includes(ext)) {
19
- fileList.push(fullPath);
20
- }
21
- }
22
- }
23
- return fileList;
24
- }
25
-
26
- function scoreLayout(filePath, projectRoot) {
27
- const content = fs.readFileSync(filePath, 'utf-8');
28
- const relativePath = path.relative(projectRoot, filePath);
29
- const depth = relativePath.split(path.sep).length;
30
- let score = 100 - depth;
31
-
32
- if (/<html/i.test(content)) score += 50;
33
- if (/<body/i.test(content)) score += 50;
34
- if (/^src[/\\]app[/\\]layout\.[jt]sx?$/.test(relativePath) || /^app[/\\]layout\.[jt]sx?$/.test(relativePath)) {
35
- score += 40;
36
- }
37
- return score;
38
- }
39
-
40
- function findRootLayout(projectRoot) {
41
- const possibleDirs = [path.join(projectRoot, 'src', 'app'), path.join(projectRoot, 'app')];
42
- const candidates = [];
43
-
44
- for (const dir of possibleDirs) {
45
- if (fs.existsSync(dir)) findLayoutFiles(dir, candidates);
46
- }
47
-
48
- if (candidates.length === 0) return null;
49
- candidates.sort((a, b) => scoreLayout(b, projectRoot) - scoreLayout(a, projectRoot));
50
- return candidates[0];
51
- }
52
-
53
- function parseArgs() {
54
- const args = process.argv.slice(2);
55
- const options = {
56
- projectRoot: process.cwd(),
57
- url: 'https://mayson.dev/',
58
- dryRun: false,
59
- help: false,
60
- };
61
-
62
- for (let i = 0; i < args.length; i++) {
63
- const arg = args[i];
64
- if (arg === '--help' || arg === '-h') {
65
- options.help = true;
66
- } else if (arg === '--dry-run') {
67
- options.dryRun = true;
68
- } else if (arg.startsWith('--url=')) {
69
- options.url = arg.split('=')[1];
70
- } else if (arg === '--url' && args[i + 1]) {
71
- options.url = args[++i];
72
- } else if (arg.startsWith('--dir=')) {
73
- options.projectRoot = path.resolve(arg.split('=')[1]);
74
- } else if (arg === '--dir' && args[i + 1]) {
75
- options.projectRoot = path.resolve(args[++i]);
76
- }
77
- }
78
- return options;
79
- }
80
-
81
- const options = parseArgs();
82
-
83
- if (options.help) {
84
- console.log(`
85
- Inject Script CLI - Next.js App Router Root Layout Iframe Injector
86
-
87
- Usage:
88
- npx inject-script [options]
89
-
90
- Options:
91
- --dir <path> Target project root directory (default: current working directory)
92
- --url <url> Iframe URL to inject (default: https://mayson.dev/)
93
- --dry-run Simulate layout detection and code injection without modifying files
94
- --help, -h Show this help message
95
- `);
96
- process.exit(0);
97
- }
98
-
99
- console.log(`🔍 Searching for Next.js root layout in: ${options.projectRoot}`);
100
-
101
- const layoutFile = findRootLayout(options.projectRoot);
102
-
103
- if (!layoutFile) {
104
- console.error(`❌ Error: Could not find any App Router layout file in ${options.projectRoot}`);
105
- process.exit(1);
106
- }
107
-
108
- console.log(`📍 Found Root Layout: ${path.relative(options.projectRoot, layoutFile)}`);
109
-
110
- const content = fs.readFileSync(layoutFile, 'utf-8');
111
- const INJECTION_MARKER = '/* @mayson-iframe-injected */';
112
-
113
- if (content.includes(INJECTION_MARKER) || content.includes(options.url)) {
114
- console.log(`✅ Mayson iframe is already injected in ${path.basename(layoutFile)}.`);
115
- process.exit(0);
116
- }
117
-
118
- const snippet = `
119
- {${INJECTION_MARKER}}
120
- <iframe
121
- src="${options.url}"
122
- style={{
123
- position: 'fixed',
124
- bottom: '20px',
125
- right: '20px',
126
- width: '400px',
127
- height: '600px',
128
- border: 'none',
129
- borderRadius: '12px',
130
- boxShadow: '0 8px 32px rgba(0, 0, 0, 0.25)',
131
- zIndex: 999999,
132
- }}
133
- title="Mayson Dev Overlay"
134
- />`;
135
-
136
- let updatedContent = null;
137
-
138
- if (/<\/body>/i.test(content)) {
139
- updatedContent = content.replace(/<\/body>/i, `${snippet}\n </body>`);
140
- } else if (/<\/html>/i.test(content)) {
141
- updatedContent = content.replace(/<\/html>/i, `${snippet}\n </html>`);
142
- } else {
143
- const lastIndex = content.lastIndexOf('</');
144
- if (lastIndex !== -1) {
145
- updatedContent = content.slice(0, lastIndex) + snippet + '\n ' + content.slice(lastIndex);
146
- }
147
- }
148
-
149
- if (!updatedContent) {
150
- console.error(`❌ Error: Could not find suitable insertion point in ${layoutFile}`);
151
- process.exit(1);
152
- }
153
-
154
- if (options.dryRun) {
155
- console.log(`🧪 [Dry Run] Would inject iframe to ${layoutFile}`);
156
- } else {
157
- fs.writeFileSync(layoutFile, updatedContent, 'utf-8');
158
- console.log(`🎉 Successfully injected Mayson iframe (${options.url}) into ${path.relative(options.projectRoot, layoutFile)}!`);
159
- }
2
+ import '../dist/cli.js';
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: string[]): Promise<void>;
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAwDA,wBAAsB,IAAI,CAAC,IAAI,WAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DtE"}
package/dist/cli.js ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { runAutoInject } from './index.js';
4
+ function printHelp() {
5
+ console.log(`
6
+ @mayson-org/inject-script — generate & inject Mayson components into Next.js App Router layout
7
+
8
+ Usage:
9
+ inject-script [options]
10
+ npx @mayson-org/inject-script [options]
11
+
12
+ Options:
13
+ --dir <path> Target project root (default: cwd)
14
+ --type <type> watermark | ascii | metadata | all (default: watermark)
15
+ --dry-run Preview without writing files
16
+ --help, -h Show help
17
+
18
+ Watermark gating (inject-time):
19
+ Fetches generated-app-metadata when MAYSON_WORKSPACE_ID, MAYSON_COLLECTION_ID,
20
+ MAYSON_API_BASE_URL, and MAYSON_WEB_TOKEN are set. Injects watermark only when
21
+ show_remix_pill is true; bakes enable_remix into the generated component.
22
+ `);
23
+ }
24
+ function parseArgs(argv) {
25
+ const options = {
26
+ projectRoot: process.cwd(),
27
+ type: 'watermark',
28
+ dryRun: false,
29
+ help: false,
30
+ };
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const arg = argv[i];
33
+ if (arg === '--help' || arg === '-h') {
34
+ options.help = true;
35
+ }
36
+ else if (arg === '--dry-run') {
37
+ options.dryRun = true;
38
+ }
39
+ else if (arg.startsWith('--type=')) {
40
+ options.type = arg.split('=')[1];
41
+ }
42
+ else if (arg === '--type' && argv[i + 1]) {
43
+ options.type = argv[++i];
44
+ }
45
+ else if (arg.startsWith('--dir=')) {
46
+ options.projectRoot = path.resolve(arg.split('=')[1]);
47
+ }
48
+ else if (arg === '--dir' && argv[i + 1]) {
49
+ options.projectRoot = path.resolve(argv[++i]);
50
+ }
51
+ else {
52
+ throw new Error(`unknown argument: ${arg}`);
53
+ }
54
+ }
55
+ return options;
56
+ }
57
+ export async function main(argv = process.argv.slice(2)) {
58
+ try {
59
+ const options = parseArgs(argv);
60
+ if (options.help) {
61
+ printHelp();
62
+ return;
63
+ }
64
+ console.log(`Searching for Next.js root layout in: ${options.projectRoot}`);
65
+ const result = await runAutoInject(options.projectRoot, [options.type], options.dryRun);
66
+ if (!result.success) {
67
+ console.error(`Error: ${result.error}`);
68
+ process.exitCode = 1;
69
+ return;
70
+ }
71
+ const relativeLayout = path.relative(options.projectRoot, result.filePath);
72
+ console.log(`Found root layout: ${relativeLayout}`);
73
+ for (const message of result.messages ?? []) {
74
+ console.log(`[mayson] ${message}`);
75
+ }
76
+ if (options.dryRun) {
77
+ console.log(`[dry-run] Would inject: ${result.injectedComponents.join(', ') || '(none)'}`);
78
+ if (result.skippedComponents.length > 0) {
79
+ console.log(`[dry-run] Already present: ${result.skippedComponents.join(', ')}`);
80
+ }
81
+ return;
82
+ }
83
+ if (result.injectedComponents.length > 0) {
84
+ console.log(`Injected: ${result.injectedComponents.join(', ')}`);
85
+ }
86
+ if (result.skippedComponents.length > 0) {
87
+ console.log(`Skipped (already present): ${result.skippedComponents.join(', ')}`);
88
+ }
89
+ if (result.injectedComponents.length === 0 &&
90
+ result.skippedComponents.length === 0 &&
91
+ (result.messages?.length ?? 0) > 0) {
92
+ console.log('Nothing to inject.');
93
+ }
94
+ else if (result.injectedComponents.length === 0 &&
95
+ result.skippedComponents.length > 0) {
96
+ console.log('Root layout is up to date.');
97
+ }
98
+ }
99
+ catch (err) {
100
+ console.error(`Error: ${err.message}`);
101
+ process.exitCode = 1;
102
+ }
103
+ }
104
+ void main();
@@ -0,0 +1,2 @@
1
+ export declare function findRootLayout(projectRoot?: string): string | null;
2
+ //# sourceMappingURL=find-root-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-root-layout.d.ts","sourceRoot":"","sources":["../../src/core/find-root-layout.ts"],"names":[],"mappings":"AAgDA,wBAAgB,cAAc,CAAC,WAAW,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CA0BjF"}
@@ -0,0 +1,61 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ const LAYOUT_EXTENSIONS = ['.tsx', '.jsx', '.ts', '.js'];
4
+ function findLayoutFiles(dir, fileList = []) {
5
+ if (!fs.existsSync(dir))
6
+ return fileList;
7
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8
+ for (const entry of entries) {
9
+ const fullPath = path.join(dir, entry.name);
10
+ if (entry.isDirectory()) {
11
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
12
+ continue;
13
+ }
14
+ findLayoutFiles(fullPath, fileList);
15
+ }
16
+ else if (entry.isFile()) {
17
+ const ext = path.extname(entry.name);
18
+ const base = path.basename(entry.name, ext);
19
+ if (base === 'layout' && LAYOUT_EXTENSIONS.includes(ext)) {
20
+ fileList.push(fullPath);
21
+ }
22
+ }
23
+ }
24
+ return fileList;
25
+ }
26
+ function scoreLayoutCandidate(filePath, projectRoot) {
27
+ const content = fs.readFileSync(filePath, 'utf-8');
28
+ const relativePath = path.relative(projectRoot, filePath);
29
+ const depth = relativePath.split(path.sep).length;
30
+ let score = 100 - depth;
31
+ if (/<html/i.test(content))
32
+ score += 50;
33
+ if (/<body/i.test(content))
34
+ score += 50;
35
+ const isDirectAppChild = /^src[/\\]app[/\\]layout\.[jt]sx?$/.test(relativePath) ||
36
+ /^app[/\\]layout\.[jt]sx?$/.test(relativePath);
37
+ if (isDirectAppChild)
38
+ score += 40;
39
+ return score;
40
+ }
41
+ export function findRootLayout(projectRoot = process.cwd()) {
42
+ const possibleAppDirs = [
43
+ path.join(projectRoot, 'src', 'app'),
44
+ path.join(projectRoot, 'app'),
45
+ ];
46
+ const candidateFiles = [];
47
+ for (const appDir of possibleAppDirs) {
48
+ if (fs.existsSync(appDir)) {
49
+ findLayoutFiles(appDir, candidateFiles);
50
+ }
51
+ }
52
+ if (candidateFiles.length === 0) {
53
+ return null;
54
+ }
55
+ const scored = candidateFiles.map((filePath) => ({
56
+ filePath,
57
+ score: scoreLayoutCandidate(filePath, projectRoot),
58
+ }));
59
+ scored.sort((a, b) => b.score - a.score);
60
+ return scored[0]?.filePath || null;
61
+ }
@@ -0,0 +1,4 @@
1
+ import type { ComponentGeneratorOptions, ComponentType, GeneratedComponentInfo } from './types.js';
2
+ export type { ComponentGeneratorOptions, ComponentType, GeneratedComponentInfo, } from './types.js';
3
+ export declare function generateComponents(types: ComponentType[] | undefined, options: ComponentGeneratorOptions): GeneratedComponentInfo[];
4
+ //# sourceMappingURL=generators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../../src/core/generators.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACb,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,yBAAyB,EACzB,aAAa,EACb,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAQpB,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,aAAa,EAAE,YAAgB,EACtC,OAAO,EAAE,yBAAyB,GACjC,sBAAsB,EAAE,CAkC1B"}
@@ -0,0 +1,37 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { plugins } from '../plugins/index.js';
4
+ const ALL_TYPES = [
5
+ 'watermark',
6
+ 'ascii',
7
+ 'metadata',
8
+ ];
9
+ export function generateComponents(types = ['watermark'], options) {
10
+ const { outputDir, isTypeScript = true, dryRun = false } = options;
11
+ const ext = isTypeScript ? '.tsx' : '.jsx';
12
+ const results = [];
13
+ if (!dryRun && !fs.existsSync(outputDir)) {
14
+ fs.mkdirSync(outputDir, { recursive: true });
15
+ }
16
+ const selectedTypes = types.includes('all')
17
+ ? ALL_TYPES
18
+ : types.filter((t) => t !== 'all');
19
+ for (const type of selectedTypes) {
20
+ const plugin = plugins[type];
21
+ if (!plugin) {
22
+ continue;
23
+ }
24
+ const code = plugin.generateSource(options.context);
25
+ const filePath = path.join(outputDir, `${plugin.name}${ext}`);
26
+ if (!dryRun) {
27
+ fs.writeFileSync(filePath, code, 'utf-8');
28
+ }
29
+ results.push({
30
+ name: plugin.name,
31
+ filePath,
32
+ importStatement: `import { ${plugin.name} } from './${plugin.name}';`,
33
+ jsxTag: `<${plugin.name} />`,
34
+ });
35
+ }
36
+ return results;
37
+ }
@@ -0,0 +1,4 @@
1
+ import type { InjectorOptions, InjectorResult } from './types.js';
2
+ export type { InjectorOptions, InjectorResult } from './types.js';
3
+ export declare function injectComponentsIntoLayout(options: InjectorOptions): InjectorResult;
4
+ //# sourceMappingURL=injector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injector.d.ts","sourceRoot":"","sources":["../../src/core/injector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAElE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAElE,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,eAAe,GAAG,cAAc,CAqFnF"}
@@ -0,0 +1,76 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export function injectComponentsIntoLayout(options) {
4
+ const { filePath, components, dryRun = false } = options;
5
+ if (!fs.existsSync(filePath)) {
6
+ return {
7
+ success: false,
8
+ filePath,
9
+ injectedComponents: [],
10
+ skippedComponents: [],
11
+ error: `Layout file not found: ${filePath}`,
12
+ };
13
+ }
14
+ let content = fs.readFileSync(filePath, 'utf-8');
15
+ const injectedComponents = [];
16
+ const skippedComponents = [];
17
+ for (const comp of components) {
18
+ const importMarker = comp.name;
19
+ const jsxMarker = `<${comp.name}`;
20
+ // Skip if already injected
21
+ if (content.includes(importMarker) || content.includes(jsxMarker)) {
22
+ skippedComponents.push(comp.name);
23
+ continue;
24
+ }
25
+ // Determine component file import path relative to layout.tsx
26
+ const layoutDir = path.dirname(filePath);
27
+ const compDir = path.dirname(comp.filePath);
28
+ const compBaseName = path.basename(comp.filePath, path.extname(comp.filePath));
29
+ let relativeImportPath = path.relative(layoutDir, path.join(compDir, compBaseName));
30
+ if (!relativeImportPath.startsWith('.')) {
31
+ relativeImportPath = `./${relativeImportPath}`;
32
+ }
33
+ // Convert backslashes for Windows path consistency
34
+ relativeImportPath = relativeImportPath.replace(/\\/g, '/');
35
+ const importLine = `import { ${comp.name} } from '${relativeImportPath}';\n`;
36
+ // 1. Insert import statement
37
+ const importMatches = Array.from(content.matchAll(/import\s+.*?from\s+['"].*?['"];?/g));
38
+ if (importMatches.length > 0) {
39
+ const lastMatch = importMatches[importMatches.length - 1];
40
+ const insertPos = lastMatch.index + lastMatch[0].length;
41
+ content = content.slice(0, insertPos) + '\n' + importLine + content.slice(insertPos);
42
+ }
43
+ else {
44
+ content = importLine + content;
45
+ }
46
+ // 2. Insert JSX tag into return block
47
+ const jsxSnippet = `\n {/* @mayson-component-injected: ${comp.name} */}\n ${comp.jsxTag}`;
48
+ if (/<\/body>/i.test(content)) {
49
+ content = content.replace(/<\/body>/i, `${jsxSnippet}\n </body>`);
50
+ }
51
+ else if (/<\/html>/i.test(content)) {
52
+ content = content.replace(/<\/html>/i, `${jsxSnippet}\n </html>`);
53
+ }
54
+ else {
55
+ const lastClosingIndex = content.lastIndexOf('</');
56
+ if (lastClosingIndex !== -1) {
57
+ content =
58
+ content.slice(0, lastClosingIndex) +
59
+ jsxSnippet +
60
+ '\n ' +
61
+ content.slice(lastClosingIndex);
62
+ }
63
+ }
64
+ injectedComponents.push(comp.name);
65
+ }
66
+ if (injectedComponents.length > 0 && !dryRun) {
67
+ fs.writeFileSync(filePath, content, 'utf-8');
68
+ }
69
+ return {
70
+ success: true,
71
+ filePath,
72
+ injectedComponents,
73
+ skippedComponents,
74
+ modifiedContent: content,
75
+ };
76
+ }
@@ -0,0 +1,48 @@
1
+ export type ComponentType = 'watermark' | 'ascii' | 'metadata' | 'all';
2
+ export interface GeneratedComponentInfo {
3
+ name: string;
4
+ filePath: string;
5
+ importStatement: string;
6
+ jsxTag: string;
7
+ }
8
+ /** Context passed into plugin generateSource (e.g. watermark flags from API). */
9
+ export type PluginGenerateContext = {
10
+ watermark?: {
11
+ showRemixPill: boolean;
12
+ enableRemix: boolean;
13
+ appBaseUrl: string;
14
+ collectionId: string;
15
+ };
16
+ };
17
+ export interface ComponentGeneratorOptions {
18
+ outputDir: string;
19
+ isTypeScript?: boolean;
20
+ /** When true, return component info without writing files. */
21
+ dryRun?: boolean;
22
+ /** Plugin template context (API / env resolved flags). */
23
+ context?: PluginGenerateContext;
24
+ }
25
+ export interface InjectorOptions {
26
+ filePath: string;
27
+ components: GeneratedComponentInfo[];
28
+ dryRun?: boolean;
29
+ }
30
+ export interface InjectorResult {
31
+ success: boolean;
32
+ filePath: string;
33
+ injectedComponents: string[];
34
+ skippedComponents: string[];
35
+ modifiedContent?: string;
36
+ error?: string;
37
+ /** Extra notes (e.g. watermark skipped because show_remix_pill is false). */
38
+ messages?: string[];
39
+ }
40
+ /** Descriptor for a generate-and-inject plugin. */
41
+ export interface MaysonPlugin {
42
+ type: Exclude<ComponentType, 'all'>;
43
+ /** Exported React component name, e.g. MaysonWatermark */
44
+ name: string;
45
+ /** Source for the component file written next to the root layout */
46
+ generateSource: (context?: PluginGenerateContext) => string;
47
+ }
48
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,KAAK,CAAC;AAEvE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,iFAAiF;AACjF,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,CAAC,EAAE;QACV,aAAa,EAAE,OAAO,CAAC;QACvB,WAAW,EAAE,OAAO,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,qBAAqB,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,sBAAsB,EAAE,CAAC;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,mDAAmD;AACnD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IACpC,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE,qBAAqB,KAAK,MAAM,CAAC;CAC7D"}