@mayson-org/inject-script 1.0.1 → 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.
- package/.env.example +9 -0
- package/README.md +65 -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 +14 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +56 -9
- package/dist/plugins/ascii/index.d.ts +3 -0
- package/dist/plugins/ascii/index.d.ts.map +1 -0
- package/dist/plugins/ascii/index.js +25 -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/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
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,41 +1,83 @@
|
|
|
1
1
|
# @mayson-org/inject-script
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
8
|
-
|
|
9
|
-
Run directly in any Next.js project root:
|
|
7
|
+
## Install
|
|
10
8
|
|
|
11
9
|
```bash
|
|
12
|
-
|
|
10
|
+
bun add -d @mayson-org/inject-script@latest
|
|
11
|
+
# or
|
|
12
|
+
npx @mayson-org/inject-script --type=watermark
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Quick usage
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
|
|
19
|
-
npx @mayson-org/inject-script --type=
|
|
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
|
-
|
|
22
|
-
npx @mayson-org/inject-script --dry-run
|
|
24
|
+
## Watermark
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
`--type=watermark` writes `MaysonWatermark.tsx` beside the root layout and injects `<MaysonWatermark />` before `</body>`.
|
|
27
|
+
|
|
28
|
+
The pill:
|
|
29
|
+
|
|
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)
|
|
33
|
+
|
|
34
|
+
### API gating (`show_remix_pill` / `enable_remix`)
|
|
35
|
+
|
|
36
|
+
At inject time the CLI fetches **generated-app-metadata** (same API as the old build plugins):
|
|
37
|
+
|
|
38
|
+
`GET {MAYSON_API_BASE_URL}/sigma/web/v1/generated-app-metadata/workspaces/{id}/collections/{id}`
|
|
39
|
+
Header: `M-Web-Token: {MAYSON_WEB_TOKEN}`
|
|
27
40
|
|
|
28
|
-
|
|
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) |
|
|
29
45
|
|
|
30
|
-
|
|
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).
|
|
31
47
|
|
|
32
|
-
|
|
33
|
-
import { runAutoInject, findRootLayout, generateComponents, injectComponentsIntoLayout } from '@mayson-org/inject-script';
|
|
48
|
+
Defaults when the API is not called or fails for other reasons: `showRemixPill=false`, `enableRemix=false` (watermark skipped).
|
|
34
49
|
|
|
35
|
-
|
|
36
|
-
const result = runAutoInject(process.cwd(), ['badge', 'watermark']);
|
|
50
|
+
### Env vars (inject-time)
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
|
|
60
|
+
## Programmatic API
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { runAutoInject } from '@mayson-org/inject-script';
|
|
64
|
+
|
|
65
|
+
const result = await runAutoInject(process.cwd(), ['watermark']);
|
|
66
|
+
```
|
|
67
|
+
|
|
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
|
|
41
77
|
```
|
|
78
|
+
|
|
79
|
+
## Notes
|
|
80
|
+
|
|
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,284 +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
|
-
const TEMPLATES = {
|
|
54
|
-
badge: {
|
|
55
|
-
name: 'MaysonBadge',
|
|
56
|
-
code: `'use client';
|
|
57
|
-
import React from 'react';
|
|
58
|
-
|
|
59
|
-
export function MaysonBadge() {
|
|
60
|
-
return (
|
|
61
|
-
<div
|
|
62
|
-
style={{
|
|
63
|
-
position: 'fixed',
|
|
64
|
-
bottom: '16px',
|
|
65
|
-
right: '16px',
|
|
66
|
-
zIndex: 99999,
|
|
67
|
-
display: 'flex',
|
|
68
|
-
alignItems: 'center',
|
|
69
|
-
gap: '8px',
|
|
70
|
-
padding: '6px 12px',
|
|
71
|
-
backgroundColor: 'rgba(18, 18, 20, 0.9)',
|
|
72
|
-
color: '#ffffff',
|
|
73
|
-
borderRadius: '9999px',
|
|
74
|
-
fontSize: '12px',
|
|
75
|
-
fontWeight: 500,
|
|
76
|
-
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
77
|
-
border: '1px solid rgba(255, 255, 255, 0.15)',
|
|
78
|
-
boxShadow: '0 4px 14px rgba(0, 0, 0, 0.25)',
|
|
79
|
-
backdropFilter: 'blur(8px)',
|
|
80
|
-
cursor: 'pointer',
|
|
81
|
-
userSelect: 'none',
|
|
82
|
-
}}
|
|
83
|
-
>
|
|
84
|
-
<span style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#6366f1' }} />
|
|
85
|
-
<span>Built with <strong>Mayson</strong></span>
|
|
86
|
-
</div>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
`,
|
|
90
|
-
},
|
|
91
|
-
watermark: {
|
|
92
|
-
name: 'MaysonWatermark',
|
|
93
|
-
code: `'use client';
|
|
94
|
-
import React from 'react';
|
|
95
|
-
|
|
96
|
-
export function MaysonWatermark() {
|
|
97
|
-
return (
|
|
98
|
-
<div
|
|
99
|
-
style={{
|
|
100
|
-
position: 'fixed',
|
|
101
|
-
bottom: '12px',
|
|
102
|
-
left: '12px',
|
|
103
|
-
zIndex: 99999,
|
|
104
|
-
opacity: 0.4,
|
|
105
|
-
fontSize: '11px',
|
|
106
|
-
fontFamily: 'monospace',
|
|
107
|
-
color: '#888888',
|
|
108
|
-
pointerEvents: 'none',
|
|
109
|
-
}}
|
|
110
|
-
>
|
|
111
|
-
⚡ Mayson Watermark
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
`,
|
|
116
|
-
},
|
|
117
|
-
ascii: {
|
|
118
|
-
name: 'MaysonAscii',
|
|
119
|
-
code: `'use client';
|
|
120
|
-
import React from 'react';
|
|
121
|
-
|
|
122
|
-
export function MaysonAscii() {
|
|
123
|
-
return (
|
|
124
|
-
<div
|
|
125
|
-
style={{
|
|
126
|
-
position: 'fixed',
|
|
127
|
-
top: '8px',
|
|
128
|
-
right: '12px',
|
|
129
|
-
zIndex: 99999,
|
|
130
|
-
fontSize: '10px',
|
|
131
|
-
fontFamily: 'monospace',
|
|
132
|
-
color: 'rgba(255, 255, 255, 0.6)',
|
|
133
|
-
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
|
134
|
-
padding: '4px 8px',
|
|
135
|
-
borderRadius: '4px',
|
|
136
|
-
pointerEvents: 'none',
|
|
137
|
-
}}
|
|
138
|
-
>
|
|
139
|
-
MAYSON :: RUNTIME ACTIVE
|
|
140
|
-
</div>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
`,
|
|
144
|
-
},
|
|
145
|
-
metadata: {
|
|
146
|
-
name: 'MaysonMetadata',
|
|
147
|
-
code: `'use client';
|
|
148
|
-
import React from 'react';
|
|
149
|
-
|
|
150
|
-
export function MaysonMetadata() {
|
|
151
|
-
return <meta name="mayson-generator" content="Mayson App Platform" />;
|
|
152
|
-
}
|
|
153
|
-
`,
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
function parseArgs() {
|
|
158
|
-
const args = process.argv.slice(2);
|
|
159
|
-
const options = {
|
|
160
|
-
projectRoot: process.cwd(),
|
|
161
|
-
type: 'badge',
|
|
162
|
-
dryRun: false,
|
|
163
|
-
help: false,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
for (let i = 0; i < args.length; i++) {
|
|
167
|
-
const arg = args[i];
|
|
168
|
-
if (arg === '--help' || arg === '-h') {
|
|
169
|
-
options.help = true;
|
|
170
|
-
} else if (arg === '--dry-run') {
|
|
171
|
-
options.dryRun = true;
|
|
172
|
-
} else if (arg.startsWith('--type=')) {
|
|
173
|
-
options.type = arg.split('=')[1];
|
|
174
|
-
} else if (arg === '--type' && args[i + 1]) {
|
|
175
|
-
options.type = args[++i];
|
|
176
|
-
} else if (arg.startsWith('--dir=')) {
|
|
177
|
-
options.projectRoot = path.resolve(arg.split('=')[1]);
|
|
178
|
-
} else if (arg === '--dir' && args[i + 1]) {
|
|
179
|
-
options.projectRoot = path.resolve(args[++i]);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return options;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const options = parseArgs();
|
|
186
|
-
|
|
187
|
-
if (options.help) {
|
|
188
|
-
console.log(`
|
|
189
|
-
@mayson-org/inject-script CLI - Generate & Inject Mayson Components into Next.js App Router
|
|
190
|
-
|
|
191
|
-
Usage:
|
|
192
|
-
npx @mayson-org/inject-script [options]
|
|
193
|
-
|
|
194
|
-
Options:
|
|
195
|
-
--dir <path> Target project root directory (default: current working directory)
|
|
196
|
-
--type <type> Component type to inject: badge | watermark | ascii | metadata | all (default: badge)
|
|
197
|
-
--dry-run Simulate component generation and layout injection without modifying files
|
|
198
|
-
--help, -h Show this help message
|
|
199
|
-
`);
|
|
200
|
-
process.exit(0);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
console.log(`🔍 Searching for Next.js root layout in: ${options.projectRoot}`);
|
|
204
|
-
|
|
205
|
-
const layoutFile = findRootLayout(options.projectRoot);
|
|
206
|
-
|
|
207
|
-
if (!layoutFile) {
|
|
208
|
-
console.error(`❌ Error: Could not find any App Router layout file in ${options.projectRoot}`);
|
|
209
|
-
process.exit(1);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
console.log(`📍 Found Root Layout: ${path.relative(options.projectRoot, layoutFile)}`);
|
|
213
|
-
|
|
214
|
-
const layoutDir = path.dirname(layoutFile);
|
|
215
|
-
const isTypeScript = layoutFile.endsWith('.tsx') || layoutFile.endsWith('.ts');
|
|
216
|
-
const ext = isTypeScript ? '.tsx' : '.jsx';
|
|
217
|
-
|
|
218
|
-
const selectedTypes = options.type === 'all' ? ['badge', 'watermark', 'ascii', 'metadata'] : [options.type];
|
|
219
|
-
|
|
220
|
-
let content = fs.readFileSync(layoutFile, 'utf-8');
|
|
221
|
-
const injectedNames = [];
|
|
222
|
-
|
|
223
|
-
for (const t of selectedTypes) {
|
|
224
|
-
const template = TEMPLATES[t];
|
|
225
|
-
if (!template) {
|
|
226
|
-
console.warn(`⚠️ Unknown component type '${t}'. Skipping.`);
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const { name, code } = template;
|
|
231
|
-
const compPath = path.join(layoutDir, `${name}${ext}`);
|
|
232
|
-
|
|
233
|
-
// Create Component File
|
|
234
|
-
if (options.dryRun) {
|
|
235
|
-
console.log(`🧪 [Dry Run] Would create component file: ${path.relative(options.projectRoot, compPath)}`);
|
|
236
|
-
} else {
|
|
237
|
-
fs.writeFileSync(compPath, code, 'utf-8');
|
|
238
|
-
console.log(`✨ Created Component File: ${path.relative(options.projectRoot, compPath)}`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Check if component already present in layout
|
|
242
|
-
if (content.includes(name)) {
|
|
243
|
-
console.log(`ℹ️ Component ${name} is already present in ${path.basename(layoutFile)}.`);
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Insert import statement
|
|
248
|
-
const importLine = `import { ${name} } from './${name}';\n`;
|
|
249
|
-
if (/import\s+.*?from\s+['"].*?['"];?/g.test(content)) {
|
|
250
|
-
const matches = Array.from(content.matchAll(/import\s+.*?from\s+['"].*?['"];?/g));
|
|
251
|
-
const last = matches[matches.length - 1];
|
|
252
|
-
const idx = last.index + last[0].length;
|
|
253
|
-
content = content.slice(0, idx) + '\n' + importLine + content.slice(idx);
|
|
254
|
-
} else {
|
|
255
|
-
content = importLine + content;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Insert JSX snippet
|
|
259
|
-
const snippet = `\n {/* @mayson-component-injected: ${name} */}\n <${name} />`;
|
|
260
|
-
|
|
261
|
-
if (/<\/body>/i.test(content)) {
|
|
262
|
-
content = content.replace(/<\/body>/i, `${snippet}\n </body>`);
|
|
263
|
-
} else if (/<\/html>/i.test(content)) {
|
|
264
|
-
content = content.replace(/<\/html>/i, `${snippet}\n </html>`);
|
|
265
|
-
} else {
|
|
266
|
-
const lastIdx = content.lastIndexOf('</');
|
|
267
|
-
if (lastIdx !== -1) {
|
|
268
|
-
content = content.slice(0, lastIdx) + snippet + '\n ' + content.slice(lastIdx);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
injectedNames.push(name);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (injectedNames.length > 0) {
|
|
276
|
-
if (options.dryRun) {
|
|
277
|
-
console.log(`🧪 [Dry Run] Would inject components into ${path.relative(options.projectRoot, layoutFile)}: ${injectedNames.join(', ')}`);
|
|
278
|
-
} else {
|
|
279
|
-
fs.writeFileSync(layoutFile, content, 'utf-8');
|
|
280
|
-
console.log(`🎉 Injected components into ${path.relative(options.projectRoot, layoutFile)}: ${injectedNames.join(', ')}`);
|
|
281
|
-
}
|
|
282
|
-
} else {
|
|
283
|
-
console.log(`✅ Root layout is up to date.`);
|
|
284
|
-
}
|
|
2
|
+
import '../dist/cli.js';
|
package/dist/cli.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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"}
|