@shipsite.dev/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.d.ts +2 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +99 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +55 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +634 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/validate.d.ts +2 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +317 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAGA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,iBAevC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
|
+
export async function add(args) {
|
|
4
|
+
const type = args[0];
|
|
5
|
+
switch (type) {
|
|
6
|
+
case 'page':
|
|
7
|
+
await addPage(args[1]);
|
|
8
|
+
break;
|
|
9
|
+
case 'blog':
|
|
10
|
+
await addBlog(args.slice(1).join(' '));
|
|
11
|
+
break;
|
|
12
|
+
default:
|
|
13
|
+
console.log('Usage: shipsite add page <name> | shipsite add blog <title>');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function addPage(name) {
|
|
17
|
+
if (!name) {
|
|
18
|
+
console.error('Error: Page name required. Usage: shipsite add page <name>');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const rootDir = process.cwd();
|
|
22
|
+
const configPath = join(rootDir, 'shipsite.json');
|
|
23
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
24
|
+
const contentDir = join(rootDir, 'content', name);
|
|
25
|
+
mkdirSync(contentDir, { recursive: true });
|
|
26
|
+
const defaultLocale = config.i18n?.defaultLocale || 'en';
|
|
27
|
+
const mdxPath = join(contentDir, `${defaultLocale}.mdx`);
|
|
28
|
+
if (!existsSync(mdxPath)) {
|
|
29
|
+
const title = name.charAt(0).toUpperCase() + name.slice(1);
|
|
30
|
+
writeFileSync(mdxPath, `---
|
|
31
|
+
title: "${title}"
|
|
32
|
+
description: ""
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<PageHero title="${title}" />
|
|
36
|
+
`);
|
|
37
|
+
console.log(` Created content/${name}/${defaultLocale}.mdx`);
|
|
38
|
+
}
|
|
39
|
+
config.pages.push({
|
|
40
|
+
slug: name,
|
|
41
|
+
type: 'page',
|
|
42
|
+
content: name,
|
|
43
|
+
locales: [defaultLocale],
|
|
44
|
+
});
|
|
45
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
46
|
+
console.log(` Added "${name}" to shipsite.json pages`);
|
|
47
|
+
}
|
|
48
|
+
async function addBlog(title) {
|
|
49
|
+
if (!title) {
|
|
50
|
+
console.error('Error: Blog title required. Usage: shipsite add blog "My Post Title"');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const rootDir = process.cwd();
|
|
54
|
+
const configPath = join(rootDir, 'shipsite.json');
|
|
55
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
56
|
+
const slug = title
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
59
|
+
.replace(/^-|-$/g, '');
|
|
60
|
+
const contentDir = join(rootDir, 'content', 'blog', slug);
|
|
61
|
+
mkdirSync(contentDir, { recursive: true });
|
|
62
|
+
const defaultLocale = config.i18n?.defaultLocale || 'en';
|
|
63
|
+
const mdxPath = join(contentDir, `${defaultLocale}.mdx`);
|
|
64
|
+
if (!existsSync(mdxPath)) {
|
|
65
|
+
const today = new Date().toISOString().split('T')[0];
|
|
66
|
+
writeFileSync(mdxPath, `---
|
|
67
|
+
title: "${title}"
|
|
68
|
+
description: ""
|
|
69
|
+
date: "${today}"
|
|
70
|
+
image: "/images/placeholder.webp"
|
|
71
|
+
readingTime: 5
|
|
72
|
+
featured: false
|
|
73
|
+
author: ""
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
<BlogArticle>
|
|
77
|
+
|
|
78
|
+
<BlogIntro>
|
|
79
|
+
Write your introduction here.
|
|
80
|
+
</BlogIntro>
|
|
81
|
+
|
|
82
|
+
## Getting Started
|
|
83
|
+
|
|
84
|
+
Your content here...
|
|
85
|
+
|
|
86
|
+
</BlogArticle>
|
|
87
|
+
`);
|
|
88
|
+
console.log(` Created content/blog/${slug}/${defaultLocale}.mdx`);
|
|
89
|
+
}
|
|
90
|
+
config.pages.push({
|
|
91
|
+
slug: `blog/${slug}`,
|
|
92
|
+
type: 'blog-article',
|
|
93
|
+
content: `blog/${slug}`,
|
|
94
|
+
locales: [defaultLocale],
|
|
95
|
+
});
|
|
96
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
97
|
+
console.log(` Added "blog/${slug}" to shipsite.json pages`);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=add.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAIA,wBAAsB,KAAK,kBAgE1B"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
export async function build() {
|
|
5
|
+
const rootDir = process.cwd();
|
|
6
|
+
const configPath = join(rootDir, 'shipsite.json');
|
|
7
|
+
if (!existsSync(configPath)) {
|
|
8
|
+
console.error('Error: shipsite.json not found in current directory');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
console.log('\n Building ShipSite...\n');
|
|
12
|
+
// 1. Validate config
|
|
13
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
14
|
+
console.log(` Config valid: ${config.name}`);
|
|
15
|
+
// 2. Validate content files
|
|
16
|
+
let missingFiles = 0;
|
|
17
|
+
for (const page of config.pages || []) {
|
|
18
|
+
const locales = page.locales || [config.i18n?.defaultLocale || 'en'];
|
|
19
|
+
for (const locale of locales) {
|
|
20
|
+
const mdxPath = join(rootDir, 'content', page.content, `${locale}.mdx`);
|
|
21
|
+
if (!existsSync(mdxPath)) {
|
|
22
|
+
console.error(` Missing: content/${page.content}/${locale}.mdx`);
|
|
23
|
+
missingFiles++;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (missingFiles > 0) {
|
|
28
|
+
console.error(`\n ${missingFiles} content file(s) missing. Aborting build.`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
console.log(` All content files present`);
|
|
32
|
+
// 3. Generate slug map
|
|
33
|
+
const { generateSlugMap } = await import('@shipsite.dev/core/generate-slug-map');
|
|
34
|
+
const shipSiteDir = join(rootDir, '.shipsite');
|
|
35
|
+
const slugMap = generateSlugMap(rootDir);
|
|
36
|
+
writeFileSync(join(shipSiteDir, 'slug-map.json'), JSON.stringify(slugMap, null, 2));
|
|
37
|
+
console.log(` Generated slug-map.json`);
|
|
38
|
+
// 4. Build
|
|
39
|
+
console.log('\n Running next build...\n');
|
|
40
|
+
const nextBuild = spawn('npx', ['next', 'build'], {
|
|
41
|
+
cwd: shipSiteDir,
|
|
42
|
+
stdio: 'inherit',
|
|
43
|
+
env: { ...process.env, SHIPSITE_ROOT: rootDir },
|
|
44
|
+
});
|
|
45
|
+
nextBuild.on('close', (code) => {
|
|
46
|
+
if (code === 0) {
|
|
47
|
+
console.log('\n Build complete!');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.error('\n Build failed');
|
|
51
|
+
}
|
|
52
|
+
process.exit(code || 0);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAWA,wBAAsB,GAAG,kBA2CxB"}
|
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
import { join, relative, dirname } from 'path';
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync, symlinkSync, readFileSync, realpathSync, } from 'fs';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
export async function dev() {
|
|
5
|
+
const rootDir = process.cwd();
|
|
6
|
+
const configPath = join(rootDir, 'shipsite.json');
|
|
7
|
+
if (!existsSync(configPath)) {
|
|
8
|
+
console.error('Error: shipsite.json not found in current directory');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
console.log('\n Starting ShipSite dev server...\n');
|
|
12
|
+
// 1. Generate .shipsite workspace
|
|
13
|
+
generateWorkspace(rootDir);
|
|
14
|
+
// 2. Generate slug map
|
|
15
|
+
const { generateSlugMap } = await import('@shipsite.dev/core/generate-slug-map');
|
|
16
|
+
const shipSiteDir = join(rootDir, '.shipsite');
|
|
17
|
+
const slugMap = generateSlugMap(rootDir);
|
|
18
|
+
writeFileSync(join(shipSiteDir, 'slug-map.json'), JSON.stringify(slugMap, null, 2));
|
|
19
|
+
console.log(` Generated slug-map.json (${Object.keys(slugMap).length} entries)`);
|
|
20
|
+
// 3. Start next dev
|
|
21
|
+
console.log('\n Starting Next.js dev server...\n');
|
|
22
|
+
const nextDev = spawn('npx', ['next', 'dev'], {
|
|
23
|
+
cwd: shipSiteDir,
|
|
24
|
+
stdio: 'inherit',
|
|
25
|
+
env: { ...process.env, SHIPSITE_ROOT: rootDir },
|
|
26
|
+
});
|
|
27
|
+
nextDev.on('close', (code) => {
|
|
28
|
+
process.exit(code || 0);
|
|
29
|
+
});
|
|
30
|
+
process.on('SIGINT', () => {
|
|
31
|
+
nextDev.kill('SIGINT');
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function hexToHsl(hex) {
|
|
35
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
36
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
37
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
38
|
+
const max = Math.max(r, g, b);
|
|
39
|
+
const min = Math.min(r, g, b);
|
|
40
|
+
const l = (max + min) / 2;
|
|
41
|
+
if (max === min)
|
|
42
|
+
return [0, 0, l * 100];
|
|
43
|
+
const d = max - min;
|
|
44
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
45
|
+
let h = 0;
|
|
46
|
+
if (max === r)
|
|
47
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
48
|
+
else if (max === g)
|
|
49
|
+
h = ((b - r) / d + 2) / 6;
|
|
50
|
+
else
|
|
51
|
+
h = ((r - g) / d + 4) / 6;
|
|
52
|
+
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
|
|
53
|
+
}
|
|
54
|
+
function generateShadcnTokens(colors) {
|
|
55
|
+
const primary = colors.primary || '#5d5bd4';
|
|
56
|
+
const accent = colors.accent || '#067647';
|
|
57
|
+
const [pH, pS, pL] = hexToHsl(primary);
|
|
58
|
+
// Light mode
|
|
59
|
+
const brandFgLight = `${pH} ${Math.min(pS + 10, 100)}% ${Math.min(pL + 10, 85)}%`;
|
|
60
|
+
// Dark mode tokens (primary design - Launch UI inspired)
|
|
61
|
+
const darkBgH = pH;
|
|
62
|
+
const darkBgS = Math.min(Math.round(pS * 0.6), 50);
|
|
63
|
+
const darkBg = `${darkBgH} ${darkBgS}% 4%`;
|
|
64
|
+
const darkFg = `0 0% 98%`;
|
|
65
|
+
const darkCard = `${darkBgH} ${darkBgS}% 6%`;
|
|
66
|
+
const darkMuted = `${darkBgH} ${Math.round(darkBgS * 0.8)}% 15%`;
|
|
67
|
+
const darkMutedFg = `${darkBgH} ${Math.round(darkBgS * 0.4)}% 65%`;
|
|
68
|
+
const darkBorder = `${darkBgH} ${Math.round(darkBgS * 0.7)}% 14%`;
|
|
69
|
+
const darkInput = `${darkBgH} ${Math.round(darkBgS * 0.7)}% 18%`;
|
|
70
|
+
const darkRing = `${pH} ${Math.round(pS * 0.5)}% 60%`;
|
|
71
|
+
const darkBrandFg = `${pH} ${Math.min(pS + 10, 100)}% ${Math.min(pL + 15, 80)}%`;
|
|
72
|
+
return `:root {
|
|
73
|
+
--brand: hsl(${pH} ${pS}% ${pL}%);
|
|
74
|
+
--brand-foreground: hsl(${brandFgLight});
|
|
75
|
+
--primary: hsl(${pH} ${pS}% ${pL}%);
|
|
76
|
+
--primary-foreground: hsl(0 0% 98%);
|
|
77
|
+
--background: hsl(0 0% 100%);
|
|
78
|
+
--foreground: hsl(222 47% 11%);
|
|
79
|
+
--card: hsl(0 0% 100%);
|
|
80
|
+
--card-foreground: hsl(222 47% 11%);
|
|
81
|
+
--popover: hsl(0 0% 100%);
|
|
82
|
+
--popover-foreground: hsl(222 47% 11%);
|
|
83
|
+
--secondary: hsl(210 10% 93%);
|
|
84
|
+
--secondary-foreground: hsl(222 47% 20%);
|
|
85
|
+
--muted: hsl(210 10% 93%);
|
|
86
|
+
--muted-foreground: hsl(215 15% 45%);
|
|
87
|
+
--accent: hsl(210 10% 90%);
|
|
88
|
+
--accent-foreground: hsl(222 47% 20%);
|
|
89
|
+
--destructive: hsl(0 72% 51%);
|
|
90
|
+
--destructive-foreground: hsl(0 72% 51%);
|
|
91
|
+
--border: hsl(220 9% 91%);
|
|
92
|
+
--input: hsl(220 9% 91%);
|
|
93
|
+
--ring: hsl(215 15% 70%);
|
|
94
|
+
--radius: 0.625rem;
|
|
95
|
+
--line-width: 1px;
|
|
96
|
+
--shadow: #00000008;
|
|
97
|
+
--shadow-strong: #00000010;
|
|
98
|
+
--ss-primary: ${primary};
|
|
99
|
+
--ss-accent: ${accent};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.dark {
|
|
103
|
+
--brand: hsl(${pH} ${pS}% ${pL}%);
|
|
104
|
+
--brand-foreground: hsl(${darkBrandFg});
|
|
105
|
+
--primary: hsl(${pH} ${pS}% ${pL}%);
|
|
106
|
+
--primary-foreground: hsl(0 0% 98%);
|
|
107
|
+
--background: hsl(${darkBg});
|
|
108
|
+
--foreground: hsl(${darkFg});
|
|
109
|
+
--card: hsl(${darkCard});
|
|
110
|
+
--card-foreground: hsl(${darkFg});
|
|
111
|
+
--popover: hsl(${darkCard});
|
|
112
|
+
--popover-foreground: hsl(${darkFg});
|
|
113
|
+
--secondary: hsl(${darkMuted});
|
|
114
|
+
--secondary-foreground: hsl(${darkFg});
|
|
115
|
+
--muted: hsl(${darkMuted});
|
|
116
|
+
--muted-foreground: hsl(${darkMutedFg});
|
|
117
|
+
--accent: hsl(${darkMuted});
|
|
118
|
+
--accent-foreground: hsl(${darkFg});
|
|
119
|
+
--destructive: hsl(0 62% 30%);
|
|
120
|
+
--destructive-foreground: hsl(${darkFg});
|
|
121
|
+
--border: hsl(${darkBorder});
|
|
122
|
+
--input: hsl(${darkInput});
|
|
123
|
+
--ring: hsl(${darkRing});
|
|
124
|
+
--radius: 0.625rem;
|
|
125
|
+
--line-width: 1px;
|
|
126
|
+
--shadow: #00000040;
|
|
127
|
+
--shadow-strong: #00000060;
|
|
128
|
+
--ss-primary: ${primary};
|
|
129
|
+
--ss-accent: ${accent};
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
function generateWorkspace(rootDir) {
|
|
134
|
+
const config = JSON.parse(readFileSync(join(rootDir, 'shipsite.json'), 'utf-8'));
|
|
135
|
+
const shipSiteDir = join(rootDir, '.shipsite');
|
|
136
|
+
if (!existsSync(shipSiteDir)) {
|
|
137
|
+
mkdirSync(shipSiteDir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
const locales = config.i18n?.locales || ['en'];
|
|
140
|
+
const defaultLocale = config.i18n?.defaultLocale || 'en';
|
|
141
|
+
const localePrefix = config.i18n?.localePrefix || 'as-needed';
|
|
142
|
+
// Symlink content directory
|
|
143
|
+
const contentLink = join(shipSiteDir, 'content');
|
|
144
|
+
if (!existsSync(contentLink)) {
|
|
145
|
+
symlinkSync(join(rootDir, 'content'), contentLink);
|
|
146
|
+
}
|
|
147
|
+
// Symlink public directory
|
|
148
|
+
const publicLink = join(shipSiteDir, 'public');
|
|
149
|
+
if (!existsSync(publicLink) && existsSync(join(rootDir, 'public'))) {
|
|
150
|
+
symlinkSync(join(rootDir, 'public'), publicLink);
|
|
151
|
+
}
|
|
152
|
+
// Symlink custom components directory
|
|
153
|
+
const hasCustomComponents = existsSync(join(rootDir, 'components'));
|
|
154
|
+
const componentsLink = join(shipSiteDir, 'components');
|
|
155
|
+
if (!existsSync(componentsLink) && hasCustomComponents) {
|
|
156
|
+
symlinkSync(join(rootDir, 'components'), componentsLink);
|
|
157
|
+
}
|
|
158
|
+
// Generate next.config.ts
|
|
159
|
+
// Detect user's custom next.config file (always expected, but graceful fallback)
|
|
160
|
+
const userNextConfigExtensions = ['ts', 'mjs', 'js'];
|
|
161
|
+
const userNextConfig = userNextConfigExtensions.find((ext) => existsSync(join(rootDir, `next.config.${ext}`)));
|
|
162
|
+
const userConfigImport = userNextConfig
|
|
163
|
+
? `import userConfig from '../next.config.${userNextConfig}';\n`
|
|
164
|
+
: '';
|
|
165
|
+
const userConfigSpread = userNextConfig ? ' ...userConfig,\n' : '';
|
|
166
|
+
writeFileSync(join(shipSiteDir, 'next.config.ts'), `import createNextIntlPlugin from 'next-intl/plugin';
|
|
167
|
+
import { withContentCollections } from '@content-collections/next';
|
|
168
|
+
import type { NextConfig } from 'next';
|
|
169
|
+
${userConfigImport}
|
|
170
|
+
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
|
|
171
|
+
|
|
172
|
+
const nextConfig: NextConfig = {
|
|
173
|
+
${userConfigSpread} reactStrictMode: true,
|
|
174
|
+
poweredByHeader: false,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export default withContentCollections(withNextIntl(nextConfig));
|
|
178
|
+
`);
|
|
179
|
+
// Generate content-collections.ts
|
|
180
|
+
writeFileSync(join(shipSiteDir, 'content-collections.ts'), `import { defineCollection, defineConfig } from '@content-collections/core';
|
|
181
|
+
import { z } from 'zod';
|
|
182
|
+
|
|
183
|
+
const sitePages = defineCollection({
|
|
184
|
+
name: 'sitePages',
|
|
185
|
+
directory: 'content',
|
|
186
|
+
include: '**/*.mdx',
|
|
187
|
+
schema: z.object({
|
|
188
|
+
content: z.string(),
|
|
189
|
+
title: z.string(),
|
|
190
|
+
description: z.string(),
|
|
191
|
+
category: z.string().optional(),
|
|
192
|
+
date: z.string().optional(),
|
|
193
|
+
image: z.string().optional(),
|
|
194
|
+
readingTime: z.number().optional(),
|
|
195
|
+
featured: z.boolean().optional(),
|
|
196
|
+
author: z.string().optional(),
|
|
197
|
+
slug: z.string().optional(),
|
|
198
|
+
}),
|
|
199
|
+
transform: (doc) => {
|
|
200
|
+
const locale = doc._meta.fileName.replace(/\\.mdx$/, '');
|
|
201
|
+
const contentFolder = doc._meta.directory;
|
|
202
|
+
const contentId = doc._meta.path.replace(/\\.mdx$/, '');
|
|
203
|
+
const path = doc._meta.directory;
|
|
204
|
+
let kind: string;
|
|
205
|
+
if (path.startsWith('blog/')) {
|
|
206
|
+
kind = path.split('/').length > 1 ? 'blog-article' : 'blog-index';
|
|
207
|
+
} else {
|
|
208
|
+
kind = 'page';
|
|
209
|
+
}
|
|
210
|
+
const raw = doc.content;
|
|
211
|
+
const match = raw.match(/<BlogIntro>\\s*([\\s\\S]*?)\\s*<\\/BlogIntro>/);
|
|
212
|
+
const excerpt = match
|
|
213
|
+
? match[1].replace(/\\*\\*(.*?)\\*\\*/g, '$1').replace(/\\[(.*?)\\]\\(.*?\\)/g, '$1').replace(/[*_~\\\`]/g, '').replace(/\\n+/g, ' ').trim()
|
|
214
|
+
: '';
|
|
215
|
+
return { ...doc, locale, contentFolder, contentId, kind, excerpt, body: { raw: doc.content } };
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
export default defineConfig({ content: [sitePages] });
|
|
220
|
+
`);
|
|
221
|
+
// Generate src directory structure
|
|
222
|
+
const srcDir = join(shipSiteDir, 'src');
|
|
223
|
+
mkdirSync(join(srcDir, 'i18n'), { recursive: true });
|
|
224
|
+
mkdirSync(join(srcDir, 'app', '[locale]', '[[...slug]]'), {
|
|
225
|
+
recursive: true,
|
|
226
|
+
});
|
|
227
|
+
mkdirSync(join(srcDir, 'styles'), { recursive: true });
|
|
228
|
+
// i18n/routing.ts
|
|
229
|
+
writeFileSync(join(srcDir, 'i18n', 'routing.ts'), `import { defineRouting } from 'next-intl/routing';
|
|
230
|
+
import { createNavigation } from 'next-intl/navigation';
|
|
231
|
+
|
|
232
|
+
export const routing = defineRouting({
|
|
233
|
+
locales: ${JSON.stringify(locales)},
|
|
234
|
+
defaultLocale: '${defaultLocale}',
|
|
235
|
+
localePrefix: '${localePrefix}',
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);
|
|
239
|
+
`);
|
|
240
|
+
// i18n/request.ts
|
|
241
|
+
writeFileSync(join(srcDir, 'i18n', 'request.ts'), `import { getRequestConfig } from 'next-intl/server';
|
|
242
|
+
import { routing } from './routing';
|
|
243
|
+
|
|
244
|
+
export default getRequestConfig(async ({ requestLocale }) => {
|
|
245
|
+
let locale = await requestLocale;
|
|
246
|
+
if (!locale || !(routing.locales as readonly string[]).includes(locale)) {
|
|
247
|
+
locale = routing.defaultLocale;
|
|
248
|
+
}
|
|
249
|
+
return { locale, messages: {} };
|
|
250
|
+
});
|
|
251
|
+
`);
|
|
252
|
+
// middleware.ts
|
|
253
|
+
writeFileSync(join(srcDir, 'middleware.ts'), `import { createShipSiteMiddleware } from '@shipsite.dev/core/middleware';
|
|
254
|
+
import slugMap from '../slug-map.json';
|
|
255
|
+
|
|
256
|
+
const middleware = createShipSiteMiddleware({
|
|
257
|
+
locales: ${JSON.stringify(locales)},
|
|
258
|
+
defaultLocale: '${defaultLocale}',
|
|
259
|
+
localePrefix: '${localePrefix}',
|
|
260
|
+
slugMap: slugMap as Record<string, Record<string, string>>,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
export default middleware;
|
|
264
|
+
|
|
265
|
+
// Next.js requires config to be a static object literal (not imported)
|
|
266
|
+
export const config = {
|
|
267
|
+
matcher: ['/((?!api|_next|_vercel|.*\\\\..*).*)'],
|
|
268
|
+
};
|
|
269
|
+
`);
|
|
270
|
+
// Resolve @shipsite.dev/components source path for Tailwind @source directive
|
|
271
|
+
const cssDir = join(srcDir, 'styles');
|
|
272
|
+
let componentsSourceDirective = '';
|
|
273
|
+
let utilsCssImport = '';
|
|
274
|
+
// Walk up from rootDir to find node_modules/@shipsite.dev/components/src
|
|
275
|
+
let searchDir = rootDir;
|
|
276
|
+
for (let i = 0; i < 10; i++) {
|
|
277
|
+
const candidate = join(searchDir, 'node_modules', '@shipsite.dev', 'components', 'src');
|
|
278
|
+
if (existsSync(candidate)) {
|
|
279
|
+
const realPath = realpathSync(candidate);
|
|
280
|
+
const rel = relative(cssDir, realPath).split('\\').join('/');
|
|
281
|
+
componentsSourceDirective = `\n@source "${rel}";`;
|
|
282
|
+
// Also resolve the utils.css path from components
|
|
283
|
+
const utilsCssPath = join(realPath, 'styles', 'utils.css');
|
|
284
|
+
if (existsSync(utilsCssPath)) {
|
|
285
|
+
const utilsRel = relative(cssDir, utilsCssPath).split('\\').join('/');
|
|
286
|
+
utilsCssImport = `\n@import "${utilsRel}";`;
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
const parent = dirname(searchDir);
|
|
291
|
+
if (parent === searchDir)
|
|
292
|
+
break;
|
|
293
|
+
searchDir = parent;
|
|
294
|
+
}
|
|
295
|
+
// Generate shadcn/ui tokens from config colors
|
|
296
|
+
const shadcnTokens = generateShadcnTokens(config.colors || {});
|
|
297
|
+
// globals.css — full Launch UI compatible stylesheet
|
|
298
|
+
writeFileSync(join(cssDir, 'globals.css'), `@import 'tailwindcss';${componentsSourceDirective}${utilsCssImport}
|
|
299
|
+
|
|
300
|
+
@import 'tw-animate-css';
|
|
301
|
+
|
|
302
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
303
|
+
|
|
304
|
+
@theme inline {
|
|
305
|
+
--color-brand: var(--brand);
|
|
306
|
+
--color-brand-foreground: var(--brand-foreground);
|
|
307
|
+
--color-background: var(--background);
|
|
308
|
+
--color-foreground: var(--foreground);
|
|
309
|
+
--color-card: var(--card);
|
|
310
|
+
--color-card-foreground: var(--card-foreground);
|
|
311
|
+
--color-popover: var(--popover);
|
|
312
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
313
|
+
--color-primary: var(--primary);
|
|
314
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
315
|
+
--color-secondary: var(--secondary);
|
|
316
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
317
|
+
--color-muted: var(--muted);
|
|
318
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
319
|
+
--color-accent: var(--accent);
|
|
320
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
321
|
+
--color-destructive: var(--destructive);
|
|
322
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
323
|
+
--color-border: var(--border);
|
|
324
|
+
--color-input: var(--input);
|
|
325
|
+
--color-ring: var(--ring);
|
|
326
|
+
|
|
327
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
328
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
329
|
+
--radius-lg: var(--radius);
|
|
330
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
331
|
+
--radius-2xl: calc(var(--radius) + 8px);
|
|
332
|
+
|
|
333
|
+
--spacing-container: 1280px;
|
|
334
|
+
--spacing-container-lg: 1536px;
|
|
335
|
+
|
|
336
|
+
--shadow-md: 0 4px 6px -1px var(--shadow), 0 2px 4px -2px var(--shadow);
|
|
337
|
+
--shadow-xl: 0 20px 25px -5px var(--shadow), 0 8px 10px -6px var(--shadow);
|
|
338
|
+
--shadow-2xl: 0 25px 50px -12px var(--shadow);
|
|
339
|
+
--shadow-mockup: -12px 16px 48px var(--shadow-strong);
|
|
340
|
+
|
|
341
|
+
--line-width: 1px;
|
|
342
|
+
|
|
343
|
+
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
344
|
+
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
345
|
+
--animate-appear: appear 0.6s forwards ease-out;
|
|
346
|
+
--animate-appear-zoom: appear-zoom 0.6s forwards ease-out;
|
|
347
|
+
|
|
348
|
+
@keyframes accordion-down {
|
|
349
|
+
from { height: 0; }
|
|
350
|
+
to { height: var(--radix-accordion-content-height); }
|
|
351
|
+
}
|
|
352
|
+
@keyframes accordion-up {
|
|
353
|
+
from { height: var(--radix-accordion-content-height); }
|
|
354
|
+
to { height: 0; }
|
|
355
|
+
}
|
|
356
|
+
@keyframes appear {
|
|
357
|
+
0% { opacity: 0; transform: translateY(1rem); filter: blur(0.5rem); }
|
|
358
|
+
50% { filter: blur(0); }
|
|
359
|
+
100% { opacity: 1; transform: translateY(0); filter: blur(0); }
|
|
360
|
+
}
|
|
361
|
+
@keyframes appear-zoom {
|
|
362
|
+
0% { opacity: 0; transform: scale(0.5); }
|
|
363
|
+
100% { opacity: 1; transform: scale(1); }
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
${shadcnTokens}
|
|
368
|
+
@layer base {
|
|
369
|
+
* {
|
|
370
|
+
@apply border-border;
|
|
371
|
+
}
|
|
372
|
+
body {
|
|
373
|
+
@apply bg-background text-foreground;
|
|
374
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
375
|
+
-webkit-font-smoothing: antialiased;
|
|
376
|
+
}
|
|
377
|
+
html { scroll-behavior: smooth; }
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.container-main {
|
|
381
|
+
width: 100%;
|
|
382
|
+
max-width: 76rem;
|
|
383
|
+
margin-inline: auto;
|
|
384
|
+
padding-inline: clamp(1rem, 3vw, 3rem);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@layer utilities {
|
|
388
|
+
@keyframes marquee {
|
|
389
|
+
from { transform: translateX(0); }
|
|
390
|
+
to { transform: translateX(calc(-100% - var(--marquee-gap))); }
|
|
391
|
+
}
|
|
392
|
+
.animate-marquee {
|
|
393
|
+
flex-shrink: 0;
|
|
394
|
+
animation: marquee var(--marquee-duration, 30s) linear infinite;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.page-prose > h2, .page-prose > h3, .page-prose > h4,
|
|
399
|
+
.page-prose > p, .page-prose > ul, .page-prose > ol,
|
|
400
|
+
.page-prose > blockquote {
|
|
401
|
+
width: 100%;
|
|
402
|
+
max-width: 76rem;
|
|
403
|
+
margin-inline: auto;
|
|
404
|
+
padding-inline: clamp(1rem, 3vw, 3rem);
|
|
405
|
+
}
|
|
406
|
+
.page-prose > h2 { margin-top: 2.5rem; margin-bottom: 1rem; font-size: clamp(1.375rem, 1.1rem + 1.2vw, 2.25rem); font-weight: 600; }
|
|
407
|
+
.page-prose > h3 { margin-top: 1.5rem; margin-bottom: 0.75rem; font-size: clamp(1.125rem, 1rem + 0.6vw, 1.5rem); font-weight: 600; }
|
|
408
|
+
.page-prose > p { font-size: 1rem; line-height: 1.6; margin-top: 0.5rem; margin-bottom: 0.5rem; }
|
|
409
|
+
.page-prose > ul { list-style: disc; padding-left: 1.75rem; margin-block: 0.75rem; }
|
|
410
|
+
.page-prose > ol { list-style: decimal; padding-left: 1.75rem; margin-block: 0.75rem; }
|
|
411
|
+
.page-prose > p a { color: var(--primary); text-decoration: underline; font-weight: 500; }
|
|
412
|
+
.page-prose > p a:hover { text-decoration: none; }
|
|
413
|
+
`);
|
|
414
|
+
// layout.tsx
|
|
415
|
+
writeFileSync(join(srcDir, 'app', '[locale]', 'layout.tsx'), `import { notFound } from 'next/navigation';
|
|
416
|
+
import { routing } from '../../i18n/routing';
|
|
417
|
+
import { ShipSiteProvider } from '@shipsite.dev/components/context';
|
|
418
|
+
import { ThemeProvider } from '@shipsite.dev/components/theme';
|
|
419
|
+
import { Header, Footer } from '@shipsite.dev/components';
|
|
420
|
+
import { generateNavLinks, generateAlternatePathMap, getConfig, getSiteUrl } from '@shipsite.dev/core';
|
|
421
|
+
import '../../styles/globals.css';
|
|
422
|
+
import type { Metadata, Viewport } from 'next';
|
|
423
|
+
|
|
424
|
+
const config = getConfig();
|
|
425
|
+
|
|
426
|
+
export function generateStaticParams() {
|
|
427
|
+
return routing.locales.map((locale) => ({ locale }));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export const metadata: Metadata = {
|
|
431
|
+
metadataBase: new URL(getSiteUrl()),
|
|
432
|
+
title: { default: config.name, template: '%s | ' + config.name },
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
export const viewport: Viewport = {
|
|
436
|
+
width: 'device-width',
|
|
437
|
+
initialScale: 1,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
interface LayoutProps {
|
|
441
|
+
children: React.ReactNode;
|
|
442
|
+
params: Promise<{ locale: string }>;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export default async function LocaleLayout({ children, params }: LayoutProps) {
|
|
446
|
+
const { locale } = await params;
|
|
447
|
+
if (!(routing.locales as readonly string[]).includes(locale)) notFound();
|
|
448
|
+
|
|
449
|
+
const navLinks = generateNavLinks(locale);
|
|
450
|
+
const alternatePathMap = generateAlternatePathMap();
|
|
451
|
+
|
|
452
|
+
return (
|
|
453
|
+
<html lang={locale} suppressHydrationWarning>
|
|
454
|
+
<body>
|
|
455
|
+
<ThemeProvider>
|
|
456
|
+
<ShipSiteProvider value={{
|
|
457
|
+
siteName: config.name,
|
|
458
|
+
siteUrl: config.url,
|
|
459
|
+
logo: config.logo,
|
|
460
|
+
ogImage: config.ogImage,
|
|
461
|
+
colors: {
|
|
462
|
+
primary: config.colors?.primary || '#5d5bd4',
|
|
463
|
+
accent: config.colors?.accent || '#067647',
|
|
464
|
+
background: config.colors?.background || '#ffffff',
|
|
465
|
+
text: config.colors?.text || '#1f2a37',
|
|
466
|
+
},
|
|
467
|
+
navigation: config.navigation || { items: [] },
|
|
468
|
+
footer: config.footer || {},
|
|
469
|
+
navLinks,
|
|
470
|
+
alternatePathMap,
|
|
471
|
+
locale,
|
|
472
|
+
locales: config.i18n?.locales || ['en'],
|
|
473
|
+
defaultLocale: config.i18n?.defaultLocale || 'en',
|
|
474
|
+
}}>
|
|
475
|
+
<Header />
|
|
476
|
+
<main id="main-content">{children}</main>
|
|
477
|
+
<Footer />
|
|
478
|
+
</ShipSiteProvider>
|
|
479
|
+
</ThemeProvider>
|
|
480
|
+
</body>
|
|
481
|
+
</html>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
`);
|
|
485
|
+
// page.tsx
|
|
486
|
+
const customComponentsImport = hasCustomComponents
|
|
487
|
+
? `import * as CustomComponents from '../../../components';\n`
|
|
488
|
+
: '';
|
|
489
|
+
const allComponentsMerge = hasCustomComponents
|
|
490
|
+
? 'const AllComponents = { ...Components, ...CustomComponents };\n'
|
|
491
|
+
: 'const AllComponents = Components;\n';
|
|
492
|
+
writeFileSync(join(srcDir, 'app', '[locale]', '[[...slug]]', 'page.tsx'), `import { setRequestLocale } from 'next-intl/server';
|
|
493
|
+
import { notFound } from 'next/navigation';
|
|
494
|
+
import { getPageContent } from '@shipsite.dev/core/mdx';
|
|
495
|
+
import { getPageBySlug, generateAllStaticParams, buildCanonicalUrl, getAlternateUrls, isNoIndexPage } from '@shipsite.dev/core/pages';
|
|
496
|
+
import { resolveAuthor } from '@shipsite.dev/core/blog';
|
|
497
|
+
import { getConfig, getSiteUrl } from '@shipsite.dev/core/config';
|
|
498
|
+
import * as Components from '@shipsite.dev/components';
|
|
499
|
+
${customComponentsImport}import type { Metadata } from 'next';
|
|
500
|
+
|
|
501
|
+
${allComponentsMerge}
|
|
502
|
+
interface PageProps {
|
|
503
|
+
params: Promise<{ locale: string; slug?: string[] }>;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export async function generateStaticParams() {
|
|
507
|
+
return generateAllStaticParams();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export const dynamicParams = false;
|
|
511
|
+
|
|
512
|
+
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
513
|
+
const { locale, slug } = await params;
|
|
514
|
+
const slugPath = slug?.join('/') || '';
|
|
515
|
+
const pageConfig = getPageBySlug(slugPath, locale);
|
|
516
|
+
if (!pageConfig) return {};
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
const { frontmatter } = await getPageContent(pageConfig.content, locale, AllComponents);
|
|
520
|
+
const canonicalUrl = buildCanonicalUrl(locale, slugPath);
|
|
521
|
+
const config = getConfig();
|
|
522
|
+
const languages = getAlternateUrls(pageConfig);
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
title: slugPath === '' ? { absolute: frontmatter.title } : frontmatter.title,
|
|
526
|
+
description: frontmatter.description,
|
|
527
|
+
...(isNoIndexPage(pageConfig) && { robots: { index: false, follow: true } }),
|
|
528
|
+
alternates: { canonical: canonicalUrl, languages },
|
|
529
|
+
openGraph: {
|
|
530
|
+
title: frontmatter.title,
|
|
531
|
+
description: frontmatter.description,
|
|
532
|
+
url: canonicalUrl,
|
|
533
|
+
siteName: config.name,
|
|
534
|
+
locale,
|
|
535
|
+
type: pageConfig.type === 'blog-article' ? 'article' : 'website',
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
} catch {
|
|
539
|
+
return {};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export default async function DynamicPage({ params }: PageProps) {
|
|
544
|
+
const { locale, slug } = await params;
|
|
545
|
+
const slugPath = slug?.join('/') || '';
|
|
546
|
+
setRequestLocale(locale);
|
|
547
|
+
|
|
548
|
+
const pageConfig = getPageBySlug(slugPath, locale);
|
|
549
|
+
if (!pageConfig) notFound();
|
|
550
|
+
|
|
551
|
+
let content;
|
|
552
|
+
try {
|
|
553
|
+
const result = await getPageContent(pageConfig.content, locale, AllComponents);
|
|
554
|
+
content = result.content;
|
|
555
|
+
} catch (error) {
|
|
556
|
+
console.error('MDX content error:', error);
|
|
557
|
+
notFound();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return <div className="page-prose">{content}</div>;
|
|
561
|
+
}
|
|
562
|
+
`);
|
|
563
|
+
// sitemap.ts
|
|
564
|
+
writeFileSync(join(srcDir, 'app', 'sitemap.ts'), `import sitemap from '@shipsite.dev/core/sitemap';
|
|
565
|
+
export default sitemap;
|
|
566
|
+
`);
|
|
567
|
+
// robots.ts
|
|
568
|
+
writeFileSync(join(srcDir, 'app', 'robots.ts'), `import type { MetadataRoute } from 'next';
|
|
569
|
+
import { getSiteUrl } from '@shipsite.dev/core/config';
|
|
570
|
+
|
|
571
|
+
export default function robots(): MetadataRoute.Robots {
|
|
572
|
+
return {
|
|
573
|
+
rules: [
|
|
574
|
+
{ userAgent: '*', allow: '/' },
|
|
575
|
+
{ userAgent: 'GPTBot', allow: '/' },
|
|
576
|
+
{ userAgent: 'ChatGPT-User', allow: '/' },
|
|
577
|
+
{ userAgent: 'ClaudeBot', allow: '/' },
|
|
578
|
+
{ userAgent: 'Claude-Web', allow: '/' },
|
|
579
|
+
{ userAgent: 'PerplexityBot', allow: '/' },
|
|
580
|
+
{ userAgent: 'Applebot-Extended', allow: '/' },
|
|
581
|
+
{ userAgent: 'GoogleOther', allow: '/' },
|
|
582
|
+
{ userAgent: 'cohere-ai', allow: '/' },
|
|
583
|
+
],
|
|
584
|
+
sitemap: getSiteUrl() + '/sitemap.xml',
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
`);
|
|
588
|
+
// tsconfig.json
|
|
589
|
+
writeFileSync(join(shipSiteDir, 'tsconfig.json'), JSON.stringify({
|
|
590
|
+
compilerOptions: {
|
|
591
|
+
target: 'ES2022',
|
|
592
|
+
lib: ['dom', 'dom.iterable', 'esnext'],
|
|
593
|
+
allowJs: true,
|
|
594
|
+
skipLibCheck: true,
|
|
595
|
+
strict: true,
|
|
596
|
+
noEmit: true,
|
|
597
|
+
esModuleInterop: true,
|
|
598
|
+
module: 'esnext',
|
|
599
|
+
moduleResolution: 'bundler',
|
|
600
|
+
resolveJsonModule: true,
|
|
601
|
+
isolatedModules: true,
|
|
602
|
+
jsx: 'preserve',
|
|
603
|
+
incremental: true,
|
|
604
|
+
plugins: [{ name: 'next' }],
|
|
605
|
+
paths: {
|
|
606
|
+
'@/*': ['./src/*'],
|
|
607
|
+
'content-collections': ['./.content-collections/generated'],
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
include: [
|
|
611
|
+
'next-env.d.ts',
|
|
612
|
+
'**/*.ts',
|
|
613
|
+
'**/*.tsx',
|
|
614
|
+
'.next/types/**/*.ts',
|
|
615
|
+
],
|
|
616
|
+
exclude: ['node_modules'],
|
|
617
|
+
}, null, 2));
|
|
618
|
+
// package.json for workspace
|
|
619
|
+
// Dependencies resolve from the parent project's node_modules via Node resolution
|
|
620
|
+
writeFileSync(join(shipSiteDir, 'package.json'), JSON.stringify({
|
|
621
|
+
name: 'shipsite-workspace',
|
|
622
|
+
private: true,
|
|
623
|
+
type: 'module',
|
|
624
|
+
}, null, 2));
|
|
625
|
+
// postcss.config.mjs
|
|
626
|
+
writeFileSync(join(shipSiteDir, 'postcss.config.mjs'), `export default {
|
|
627
|
+
plugins: {
|
|
628
|
+
'@tailwindcss/postcss': {},
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
`);
|
|
632
|
+
console.log(' Generated .shipsite workspace');
|
|
633
|
+
}
|
|
634
|
+
//# sourceMappingURL=dev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AA8IA,wBAAsB,QAAQ,kBAsS7B"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { join, relative } from 'path';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
3
|
+
const errors = [];
|
|
4
|
+
const warnings = [];
|
|
5
|
+
function fail(message) {
|
|
6
|
+
errors.push(message);
|
|
7
|
+
}
|
|
8
|
+
function warn(message) {
|
|
9
|
+
warnings.push(message);
|
|
10
|
+
}
|
|
11
|
+
function parseFrontmatter(source) {
|
|
12
|
+
const match = source.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
|
|
13
|
+
if (!match)
|
|
14
|
+
return null;
|
|
15
|
+
const raw = match[1];
|
|
16
|
+
const data = {};
|
|
17
|
+
for (const line of raw.split('\n')) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
20
|
+
continue;
|
|
21
|
+
const idx = trimmed.indexOf(':');
|
|
22
|
+
if (idx === -1)
|
|
23
|
+
continue;
|
|
24
|
+
const key = trimmed.slice(0, idx).trim();
|
|
25
|
+
let value = trimmed.slice(idx + 1).trim();
|
|
26
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
27
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
28
|
+
value = value.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
if (key) {
|
|
31
|
+
data[key] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
function extractComponentProps(source, componentName) {
|
|
37
|
+
const match = source.match(new RegExp(`<${componentName}\\b([\\s\\S]*?)>`));
|
|
38
|
+
if (!match)
|
|
39
|
+
return null;
|
|
40
|
+
const raw = match[1] || '';
|
|
41
|
+
const props = new Set();
|
|
42
|
+
const propRegex = /([A-Za-z][A-Za-z0-9_]*)\s*=/g;
|
|
43
|
+
let propMatch;
|
|
44
|
+
while ((propMatch = propRegex.exec(raw))) {
|
|
45
|
+
props.add(propMatch[1]);
|
|
46
|
+
}
|
|
47
|
+
return props;
|
|
48
|
+
}
|
|
49
|
+
function requireComponent(source, componentName, filePath) {
|
|
50
|
+
const props = extractComponentProps(source, componentName);
|
|
51
|
+
if (!props) {
|
|
52
|
+
fail(`Missing <${componentName}> in: ${filePath}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return props;
|
|
56
|
+
}
|
|
57
|
+
function requireProps(props, required, componentName, filePath) {
|
|
58
|
+
for (const prop of required) {
|
|
59
|
+
if (!props.has(prop)) {
|
|
60
|
+
fail(`Missing prop "${prop}" in <${componentName}>: ${filePath}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function warnOnSeo(frontmatter, filePath) {
|
|
65
|
+
const title = frontmatter.title || '';
|
|
66
|
+
const description = frontmatter.description || '';
|
|
67
|
+
const titleLength = title.trim().length;
|
|
68
|
+
const descriptionLength = description.trim().length;
|
|
69
|
+
if (titleLength < 30 || titleLength > 70) {
|
|
70
|
+
warn(`SEO title length (${titleLength}) out of range 30-70: ${filePath}`);
|
|
71
|
+
}
|
|
72
|
+
if (descriptionLength < 70 || descriptionLength > 160) {
|
|
73
|
+
warn(`SEO description length (${descriptionLength}) out of range 70-160: ${filePath}`);
|
|
74
|
+
}
|
|
75
|
+
if (description.endsWith('...')) {
|
|
76
|
+
warn(`SEO description ends with ellipsis: ${filePath}`);
|
|
77
|
+
}
|
|
78
|
+
if (title.toLowerCase() === description.toLowerCase()) {
|
|
79
|
+
warn(`SEO title equals description: ${filePath}`);
|
|
80
|
+
}
|
|
81
|
+
const descriptionLower = description.toLowerCase().trim();
|
|
82
|
+
const titleLower = title.toLowerCase().trim();
|
|
83
|
+
if (descriptionLower.startsWith(titleLower) && titleLower.length > 0) {
|
|
84
|
+
warn(`SEO description starts with title: ${filePath}`);
|
|
85
|
+
}
|
|
86
|
+
const punctuation = /[.!?]$/;
|
|
87
|
+
if (descriptionLength > 0 && !punctuation.test(description.trim())) {
|
|
88
|
+
warn(`SEO description should end with punctuation: ${filePath}`);
|
|
89
|
+
}
|
|
90
|
+
const stopwords = new Set([
|
|
91
|
+
'the', 'and', 'or', 'for', 'with', 'from', 'that', 'this',
|
|
92
|
+
'your', 'you', 'are', 'our', 'to', 'of', 'in', 'a', 'an',
|
|
93
|
+
]);
|
|
94
|
+
const tokens = descriptionLower.split(/[^a-z0-9]+/).filter(Boolean);
|
|
95
|
+
const counts = new Map();
|
|
96
|
+
for (const token of tokens) {
|
|
97
|
+
if (token.length < 3 || stopwords.has(token))
|
|
98
|
+
continue;
|
|
99
|
+
counts.set(token, (counts.get(token) || 0) + 1);
|
|
100
|
+
}
|
|
101
|
+
for (const [token, count] of counts.entries()) {
|
|
102
|
+
if (count >= 4) {
|
|
103
|
+
warn(`SEO description repeats "${token}" ${count}x: ${filePath}`);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export async function validate() {
|
|
109
|
+
const rootDir = process.cwd();
|
|
110
|
+
const configPath = join(rootDir, 'shipsite.json');
|
|
111
|
+
const contentDir = join(rootDir, 'content');
|
|
112
|
+
console.log('\n Validating content...\n');
|
|
113
|
+
if (!existsSync(configPath)) {
|
|
114
|
+
console.error(' Error: shipsite.json not found in current directory');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
if (!existsSync(contentDir)) {
|
|
118
|
+
console.error(' Error: content/ directory not found');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
122
|
+
const pages = config.pages || [];
|
|
123
|
+
const blogAuthors = Object.keys(config.blog?.authors || {});
|
|
124
|
+
const contentSet = new Set(pages.map((page) => page.content));
|
|
125
|
+
// Track titles/descriptions per locale for duplicate detection
|
|
126
|
+
const titlesByLocale = new Map();
|
|
127
|
+
const descriptionsByLocale = new Map();
|
|
128
|
+
for (const page of pages) {
|
|
129
|
+
if (!page.content) {
|
|
130
|
+
fail(`Page is missing "content" path for slug "${page.slug}"`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const pageDir = join(contentDir, page.content);
|
|
134
|
+
if (!existsSync(pageDir)) {
|
|
135
|
+
fail(`Missing content folder: content/${page.content}`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const pageLocales = page.locales || [config.i18n?.defaultLocale || 'en'];
|
|
139
|
+
for (const locale of pageLocales) {
|
|
140
|
+
const mdxPath = join(pageDir, `${locale}.mdx`);
|
|
141
|
+
if (!existsSync(mdxPath)) {
|
|
142
|
+
fail(`Missing MDX file: content/${page.content}/${locale}.mdx`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const source = readFileSync(mdxPath, 'utf-8');
|
|
146
|
+
const rel = relative(contentDir, mdxPath);
|
|
147
|
+
const frontmatter = parseFrontmatter(source);
|
|
148
|
+
if (!frontmatter) {
|
|
149
|
+
fail(`Missing frontmatter in: content/${rel}`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (!frontmatter.title) {
|
|
153
|
+
fail(`Missing "title" in frontmatter: content/${rel}`);
|
|
154
|
+
}
|
|
155
|
+
if (!frontmatter.description) {
|
|
156
|
+
fail(`Missing "description" in frontmatter: content/${rel}`);
|
|
157
|
+
}
|
|
158
|
+
warnOnSeo(frontmatter, `content/${rel}`);
|
|
159
|
+
// Collect titles/descriptions for duplicate detection
|
|
160
|
+
if (frontmatter.title) {
|
|
161
|
+
if (!titlesByLocale.has(locale))
|
|
162
|
+
titlesByLocale.set(locale, new Map());
|
|
163
|
+
const titles = titlesByLocale.get(locale);
|
|
164
|
+
const key = frontmatter.title.trim();
|
|
165
|
+
if (!titles.has(key))
|
|
166
|
+
titles.set(key, []);
|
|
167
|
+
titles.get(key).push(`content/${rel}`);
|
|
168
|
+
}
|
|
169
|
+
if (frontmatter.description) {
|
|
170
|
+
if (!descriptionsByLocale.has(locale))
|
|
171
|
+
descriptionsByLocale.set(locale, new Map());
|
|
172
|
+
const descriptions = descriptionsByLocale.get(locale);
|
|
173
|
+
const key = frontmatter.description.trim();
|
|
174
|
+
if (!descriptions.has(key))
|
|
175
|
+
descriptions.set(key, []);
|
|
176
|
+
descriptions.get(key).push(`content/${rel}`);
|
|
177
|
+
}
|
|
178
|
+
// Blog slugs must not contain "blog/" prefix
|
|
179
|
+
const parts = page.content.split('/');
|
|
180
|
+
if (parts[0] === 'blog' &&
|
|
181
|
+
frontmatter.slug &&
|
|
182
|
+
frontmatter.slug.startsWith('blog/')) {
|
|
183
|
+
fail(`Blog slug "${frontmatter.slug}" must not start with "blog/": content/${rel}`);
|
|
184
|
+
}
|
|
185
|
+
// Raw <img> tags
|
|
186
|
+
if (/<img\b(?![^>]*\bsrc\s*=\s*["']https?:\/\/)/i.test(source)) {
|
|
187
|
+
fail(`Raw <img> tag found — use MDX components: content/${rel}`);
|
|
188
|
+
}
|
|
189
|
+
// --- Component checks per page type ---
|
|
190
|
+
if (page.type === 'landing') {
|
|
191
|
+
const props = requireComponent(source, 'Hero', `content/${rel}`);
|
|
192
|
+
if (props) {
|
|
193
|
+
requireProps(props, ['title', 'description'], 'Hero', `content/${rel}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (page.type === 'blog-index') {
|
|
197
|
+
const props = requireComponent(source, 'BlogIndex', `content/${rel}`);
|
|
198
|
+
if (props) {
|
|
199
|
+
requireProps(props, ['title', 'description'], 'BlogIndex', `content/${rel}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (page.type === 'blog-article') {
|
|
203
|
+
requireComponent(source, 'BlogArticle', `content/${rel}`);
|
|
204
|
+
// Required frontmatter fields
|
|
205
|
+
const requiredBlogFields = ['date', 'author', 'readingTime'];
|
|
206
|
+
for (const field of requiredBlogFields) {
|
|
207
|
+
if (!frontmatter[field]) {
|
|
208
|
+
fail(`Missing frontmatter "${field}" for blog article: content/${rel}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!frontmatter.image) {
|
|
212
|
+
warn(`Missing frontmatter "image" for blog article (fallback will be used): content/${rel}`);
|
|
213
|
+
}
|
|
214
|
+
// Validate author exists in shipsite.json
|
|
215
|
+
if (frontmatter.author && !blogAuthors.includes(frontmatter.author)) {
|
|
216
|
+
fail(`Unknown author "${frontmatter.author}" (not in shipsite.json blog.authors): content/${rel}`);
|
|
217
|
+
}
|
|
218
|
+
// Validate date format YYYY-MM-DD
|
|
219
|
+
if (frontmatter.date &&
|
|
220
|
+
!/^\d{4}-\d{2}-\d{2}$/.test(frontmatter.date)) {
|
|
221
|
+
fail(`Invalid date format "${frontmatter.date}" (expected YYYY-MM-DD): content/${rel}`);
|
|
222
|
+
}
|
|
223
|
+
// Validate readingTime is a number
|
|
224
|
+
if (frontmatter.readingTime && isNaN(Number(frontmatter.readingTime))) {
|
|
225
|
+
fail(`readingTime must be a number: content/${rel}`);
|
|
226
|
+
}
|
|
227
|
+
// Validate image file exists
|
|
228
|
+
if (frontmatter.image) {
|
|
229
|
+
const imagePath = join(rootDir, 'public', frontmatter.image);
|
|
230
|
+
if (!existsSync(imagePath)) {
|
|
231
|
+
warn(`Blog image not found: public${frontmatter.image} (referenced in content/${rel})`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Minimum word count (300 words)
|
|
235
|
+
const bodyContent = source
|
|
236
|
+
.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '')
|
|
237
|
+
.replace(/<[^>]+>/g, '')
|
|
238
|
+
.replace(/import\s+.*?from\s+['"][^'"]+['"]\s*;?/g, '')
|
|
239
|
+
.trim();
|
|
240
|
+
const wordCount = bodyContent.split(/\s+/).filter(Boolean).length;
|
|
241
|
+
if (wordCount < 300) {
|
|
242
|
+
warn(`Blog article has only ${wordCount} words (minimum 300): content/${rel}`);
|
|
243
|
+
}
|
|
244
|
+
// Heading structure validation
|
|
245
|
+
const headingLines = source
|
|
246
|
+
.split('\n')
|
|
247
|
+
.filter((l) => /^#{2,6}\s/.test(l));
|
|
248
|
+
const hasH2 = headingLines.some((l) => /^##\s/.test(l));
|
|
249
|
+
if (!hasH2) {
|
|
250
|
+
warn(`Blog article has no ## headings: content/${rel}`);
|
|
251
|
+
}
|
|
252
|
+
const firstH2Index = headingLines.findIndex((l) => /^##\s/.test(l));
|
|
253
|
+
const firstH3Index = headingLines.findIndex((l) => /^###\s/.test(l));
|
|
254
|
+
if (firstH3Index !== -1 &&
|
|
255
|
+
(firstH2Index === -1 || firstH3Index < firstH2Index)) {
|
|
256
|
+
warn(`Blog article has ### before first ##: content/${rel}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Component-level checks by presence (any page type)
|
|
260
|
+
const bannerProps = extractComponentProps(source, 'BannerCTA');
|
|
261
|
+
if (bannerProps) {
|
|
262
|
+
requireProps(bannerProps, ['title', 'buttonText'], 'BannerCTA', `content/${rel}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Cross-page duplicate title/description detection
|
|
267
|
+
for (const [locale, titles] of titlesByLocale.entries()) {
|
|
268
|
+
for (const [title, files] of titles.entries()) {
|
|
269
|
+
if (files.length > 1) {
|
|
270
|
+
warn(`Duplicate title "${title}" in locale "${locale}": ${files.join(', ')}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (const [locale, descriptions] of descriptionsByLocale.entries()) {
|
|
275
|
+
for (const [, files] of descriptions.entries()) {
|
|
276
|
+
if (files.length > 1) {
|
|
277
|
+
warn(`Duplicate description in locale "${locale}": ${files.join(', ')}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Warn about orphan content not in shipsite.json
|
|
282
|
+
function walkContentDirs(dir, prefix) {
|
|
283
|
+
for (const entry of readdirSync(dir)) {
|
|
284
|
+
if (entry.startsWith('_') || entry.startsWith('.'))
|
|
285
|
+
continue;
|
|
286
|
+
const entryPath = join(dir, entry);
|
|
287
|
+
if (!statSync(entryPath).isDirectory())
|
|
288
|
+
continue;
|
|
289
|
+
const contentPath = prefix ? `${prefix}/${entry}` : entry;
|
|
290
|
+
const hasMdx = readdirSync(entryPath).some((f) => f.endsWith('.mdx'));
|
|
291
|
+
if (hasMdx && !contentSet.has(contentPath)) {
|
|
292
|
+
warn(`Content folder not referenced in shipsite.json pages: content/${contentPath}`);
|
|
293
|
+
}
|
|
294
|
+
walkContentDirs(entryPath, contentPath);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
walkContentDirs(contentDir, '');
|
|
298
|
+
// Output results
|
|
299
|
+
if (warnings.length) {
|
|
300
|
+
console.warn(' Warnings:');
|
|
301
|
+
for (const message of warnings) {
|
|
302
|
+
console.warn(` - ${message}`);
|
|
303
|
+
}
|
|
304
|
+
console.log();
|
|
305
|
+
}
|
|
306
|
+
if (errors.length) {
|
|
307
|
+
console.error(' Errors:');
|
|
308
|
+
for (const message of errors) {
|
|
309
|
+
console.error(` - ${message}`);
|
|
310
|
+
}
|
|
311
|
+
console.log();
|
|
312
|
+
console.error(` Validation failed: ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
console.log(` Validation passed. (${warnings.length} warning${warnings.length !== 1 ? 's' : ''})`);
|
|
316
|
+
}
|
|
317
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,QAAA,MAAM,IAAI,UAAwB,CAAC;AACnC,QAAA,MAAM,OAAO,QAAU,CAAC;AAExB,iBAAe,IAAI,kBAsClB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
const command = args[0];
|
|
5
|
+
async function main() {
|
|
6
|
+
switch (command) {
|
|
7
|
+
case 'dev': {
|
|
8
|
+
const { dev } = await import('./commands/dev.js');
|
|
9
|
+
await dev();
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
case 'build': {
|
|
13
|
+
const { build } = await import('./commands/build.js');
|
|
14
|
+
await build();
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
case 'add': {
|
|
18
|
+
const { add } = await import('./commands/add.js');
|
|
19
|
+
await add(args.slice(1));
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
case 'validate': {
|
|
23
|
+
const { validate } = await import('./commands/validate.js');
|
|
24
|
+
await validate();
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
default:
|
|
28
|
+
console.log(`
|
|
29
|
+
ShipSite CLI
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
shipsite dev Start development server
|
|
33
|
+
shipsite build Build for production
|
|
34
|
+
shipsite add page <name> Add a new page
|
|
35
|
+
shipsite add blog <title> Add a new blog post
|
|
36
|
+
shipsite validate Validate content and SEO
|
|
37
|
+
|
|
38
|
+
Options:
|
|
39
|
+
--help Show this help message
|
|
40
|
+
--version Show version
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
main().catch((err) => {
|
|
45
|
+
console.error(err);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAEA,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAExB,KAAK,UAAU,IAAI;IACjB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;YAClD,MAAM,GAAG,EAAE,CAAC;YACZ,MAAM;QACR,CAAC;QACD,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;YACtD,MAAM,KAAK,EAAE,CAAC;YACd,MAAM;QACR,CAAC;QACD,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;YAClD,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACzB,MAAM;QACR,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;YAC5D,MAAM,QAAQ,EAAE,CAAC;YACjB,MAAM;QACR,CAAC;QACD;YACE,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;OAaX,CAAC,CAAC;IACP,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shipsite.dev/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"shipsite": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"clean": "rm -rf dist"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@shipsite.dev/core": "^0.1.0",
|
|
18
|
+
"chokidar": "^4.0.3"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"typescript": "^5.8.3",
|
|
22
|
+
"@types/node": "^20"
|
|
23
|
+
}
|
|
24
|
+
}
|