idcmd 0.0.7 → 0.0.9
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/README.md +16 -16
- package/package.json +4 -4
- package/src/build.ts +38 -36
- package/src/cli/commands/build.ts +3 -3
- package/src/cli/commands/client.ts +6 -8
- package/src/cli/commands/deploy.ts +7 -7
- package/src/cli/commands/dev.ts +3 -3
- package/src/cli/commands/init.ts +1 -1
- package/src/cli/commands/preview.ts +3 -3
- package/src/cli/runtime-assets.ts +2 -2
- package/src/content/paths.ts +1 -1
- package/src/project/paths.ts +18 -27
- package/src/render/layout-loader.ts +1 -1
- package/src/render/right-rail-loader.ts +1 -1
- package/src/search/index.ts +1 -1
- package/src/search/search-page-loader.ts +1 -1
- package/src/seo/server.ts +6 -6
- package/src/server/live-reload.ts +1 -1
- package/src/server/static.ts +3 -3
- package/src/server.ts +5 -5
- package/src/site/config.ts +1 -1
- package/templates/default/README.md +13 -13
- package/templates/default/{site/content → content}/index.md +1 -1
- package/templates/default/scripts/check-internal.ts +6 -6
- package/templates/default/scripts/check.ts +6 -6
- package/templates/default/src/runtime/live-reload.ts +18 -0
- package/templates/default/src/runtime/llm-menu.ts +162 -0
- package/templates/default/src/runtime/nav-prefetch.ts +30 -0
- package/templates/default/src/runtime/right-rail-scrollspy.ts +303 -0
- package/templates/default/{site/code → src}/server.ts +1 -1
- package/templates/default/vercel.json +1 -1
- package/public/anthropic-black.svg +0 -16
- package/public/openai-black.svg +0 -15
- package/templates/default/site/assets/anthropic-white.svg +0 -16
- package/templates/default/site/assets/favicon.svg +0 -13
- package/templates/default/site/assets/openai-white.svg +0 -15
- /package/{templates/default/site/code → src}/runtime/live-reload.ts +0 -0
- /package/{templates/default/site/code → src}/runtime/llm-menu.ts +0 -0
- /package/{templates/default/site/code → src}/runtime/nav-prefetch.ts +0 -0
- /package/{templates/default/site/code → src}/runtime/right-rail-scrollspy.ts +0 -0
- /package/{public → templates/default/assets}/anthropic-white.svg +0 -0
- /package/{public → templates/default/assets}/favicon.svg +0 -0
- /package/templates/default/{site/assets → assets}/icons/file.svg +0 -0
- /package/templates/default/{site/assets → assets}/icons/home.svg +0 -0
- /package/templates/default/{site/assets → assets}/icons/info.svg +0 -0
- /package/{public → templates/default/assets}/openai-white.svg +0 -0
- /package/templates/default/{site/content → content}/404.md +0 -0
- /package/templates/default/{site/content → content}/about.md +0 -0
- /package/templates/default/{site/site.jsonc → site.jsonc} +0 -0
- /package/templates/default/{site/code → src}/routes/api/hello.ts +0 -0
- /package/templates/default/{site/code → src}/ui/layout.tsx +0 -0
- /package/templates/default/{site/code → src}/ui/right-rail.tsx +0 -0
- /package/templates/default/{site/code → src}/ui/search-page.tsx +0 -0
- /package/templates/default/{site/styles → styles}/tailwind.css +0 -0
package/README.md
CHANGED
|
@@ -9,29 +9,29 @@ bun install
|
|
|
9
9
|
bun run dev
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Everything you edit lives
|
|
12
|
+
Everything you edit lives at the project root-level source folders (`content/`, `src/`, `styles/`, `assets/`, `site.jsonc`).
|
|
13
13
|
|
|
14
14
|
## CLI
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
idcmd init [dir] # scaffold a new site
|
|
18
18
|
idcmd dev # tailwind watch + SSR dev server
|
|
19
|
-
idcmd build # static
|
|
20
|
-
idcmd preview # serve
|
|
19
|
+
idcmd build # static public/
|
|
20
|
+
idcmd preview # serve public/ locally
|
|
21
21
|
idcmd deploy # build + validate Vercel static deploy config
|
|
22
|
-
idcmd client ... # add/update local
|
|
22
|
+
idcmd client ... # add/update local src implementations
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Layout (V1)
|
|
26
26
|
|
|
27
|
-
- `
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
- `
|
|
32
|
-
- `
|
|
33
|
-
- `site
|
|
34
|
-
- `
|
|
27
|
+
- `content/<slug>.md` -> `/<slug>/` (`index.md` -> `/`)
|
|
28
|
+
- `src/ui/*` is local UI source code (you own and edit these files)
|
|
29
|
+
- `src/runtime/*.ts` is local browser runtime code (compiled to `public/_idcmd/*.js`)
|
|
30
|
+
- `src/routes/**` file-based server routes (dev/server-host only)
|
|
31
|
+
- `styles/tailwind.css` -> `public/styles.css`
|
|
32
|
+
- `assets/` static assets
|
|
33
|
+
- `site.jsonc` site config
|
|
34
|
+
- `public/` generated output (gitignored)
|
|
35
35
|
|
|
36
36
|
## Syncing Local Client Code
|
|
37
37
|
|
|
@@ -45,11 +45,11 @@ idcmd client update runtime --yes
|
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
`add` creates missing files. `update` overwrites changed files and requires `--yes` unless `--dry-run` is used.
|
|
48
|
-
Runtime files in `
|
|
48
|
+
Runtime files in `src/runtime/` are compiled automatically by `idcmd dev` and `idcmd build`.
|
|
49
49
|
|
|
50
50
|
## Example: Add A Page
|
|
51
51
|
|
|
52
|
-
Create `
|
|
52
|
+
Create `content/hello.md`:
|
|
53
53
|
|
|
54
54
|
```md
|
|
55
55
|
---
|
|
@@ -68,7 +68,7 @@ It renders at `/hello/`.
|
|
|
68
68
|
|
|
69
69
|
## Custom Server Routes (V1)
|
|
70
70
|
|
|
71
|
-
Add `
|
|
71
|
+
Add `src/routes/api/hello.ts`:
|
|
72
72
|
|
|
73
73
|
```ts
|
|
74
74
|
export const GET = (): Response => Response.json({ ok: true });
|
|
@@ -94,7 +94,7 @@ It responds at `/api/hello`.
|
|
|
94
94
|
|
|
95
95
|
### Slug and path rules
|
|
96
96
|
|
|
97
|
-
- Content lives at `
|
|
97
|
+
- Content lives at `content/<slug>.md`.
|
|
98
98
|
- `slug="index"` is the home page.
|
|
99
99
|
- Canonical HTML paths are `/` for index and `/<slug>/` otherwise.
|
|
100
100
|
- Markdown download paths exist in two forms:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idcmd",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/rustydotwtf/idcmd"
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"dev": "concurrently -k -n css,server -c blue,green \"bun run dev:css\" \"bun run dev:server\"",
|
|
25
|
-
"dev:css": "bunx @tailwindcss/cli -i
|
|
25
|
+
"dev:css": "bunx @tailwindcss/cli -i styles/tailwind.css -o public/styles.css --watch",
|
|
26
26
|
"dev:server": "bun --hot src/server.ts",
|
|
27
|
-
"build:css": "bunx @tailwindcss/cli -i
|
|
27
|
+
"build:css": "bunx @tailwindcss/cli -i styles/tailwind.css -o public/styles.css --minify",
|
|
28
28
|
"build": "bun run build:css && bun src/build.ts",
|
|
29
|
-
"preview": "bunx serve
|
|
29
|
+
"preview": "bunx serve public",
|
|
30
30
|
"start": "bun src/server.ts",
|
|
31
31
|
"check": "bun run scripts/check.ts",
|
|
32
32
|
"test": "bun test",
|
package/src/build.ts
CHANGED
|
@@ -20,7 +20,7 @@ const MIN_SEARCH_QUERY_LENGTH = 2;
|
|
|
20
20
|
|
|
21
21
|
const project = await getProjectPaths();
|
|
22
22
|
|
|
23
|
-
// Find all content files in `
|
|
23
|
+
// Find all content files in `content/` (`content/<slug>.md`).
|
|
24
24
|
const contentFiles: string[] = [];
|
|
25
25
|
|
|
26
26
|
const buildStart = performance.now();
|
|
@@ -40,26 +40,26 @@ console.log(
|
|
|
40
40
|
`Found ${navigation.length} groups with ${navigation.reduce((acc, g) => acc + g.items.length, 0)} total pages`
|
|
41
41
|
);
|
|
42
42
|
|
|
43
|
-
// Ensure
|
|
44
|
-
await Bun.write(`${project.
|
|
43
|
+
// Ensure output directory exists
|
|
44
|
+
await Bun.write(`${project.outputDir}/.gitkeep`, "");
|
|
45
45
|
|
|
46
|
-
const
|
|
47
|
-
// Do not overwrite the minified `
|
|
46
|
+
const shouldCopyAssetPath = (relativePath: string): boolean =>
|
|
47
|
+
// Do not overwrite the minified `public/styles.css` produced by `build:css`.
|
|
48
48
|
relativePath !== "styles.css";
|
|
49
49
|
|
|
50
|
-
const
|
|
51
|
-
const file = Bun.file(`${project.
|
|
50
|
+
const copyAssetFileToOutput = async (relativePath: string): Promise<void> => {
|
|
51
|
+
const file = Bun.file(`${project.assetsDir}/${relativePath}`);
|
|
52
52
|
if (!(await file.exists())) {
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
|
-
await Bun.write(`${project.
|
|
55
|
+
await Bun.write(`${project.outputDir}/${relativePath}`, file);
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
for await (const relativePath of
|
|
61
|
-
if (
|
|
62
|
-
await
|
|
58
|
+
const copyAssetsToOutput = async (): Promise<void> => {
|
|
59
|
+
const assetFiles = new Bun.Glob("**/*").scan(project.assetsDir);
|
|
60
|
+
for await (const relativePath of assetFiles) {
|
|
61
|
+
if (shouldCopyAssetPath(relativePath)) {
|
|
62
|
+
await copyAssetFileToOutput(relativePath);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
};
|
|
@@ -70,8 +70,8 @@ const writeMarkdownOutputs = async (
|
|
|
70
70
|
): Promise<void> => {
|
|
71
71
|
const flatMarkdownPath =
|
|
72
72
|
slug === "index"
|
|
73
|
-
? `${project.
|
|
74
|
-
: `${project.
|
|
73
|
+
? `${project.outputDir}/index.md`
|
|
74
|
+
: `${project.outputDir}/${slug}.md`;
|
|
75
75
|
|
|
76
76
|
const expanded = await expandMarkdownForAgent(markdown, {
|
|
77
77
|
currentPath: slug === "index" ? "/" : `/${slug}/`,
|
|
@@ -84,14 +84,14 @@ const writeMarkdownOutputs = async (
|
|
|
84
84
|
console.log(` markdown -> ${flatMarkdownPath}`);
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
-
await
|
|
87
|
+
await copyAssetsToOutput();
|
|
88
88
|
|
|
89
89
|
const resolveCssSource = async (): Promise<string | null> => {
|
|
90
|
-
if (await Bun.file(`${project.
|
|
91
|
-
return `${project.
|
|
90
|
+
if (await Bun.file(`${project.outputDir}/styles.css`).exists()) {
|
|
91
|
+
return `${project.outputDir}/styles.css`;
|
|
92
92
|
}
|
|
93
|
-
if (await Bun.file(`${project.
|
|
94
|
-
return `${project.
|
|
93
|
+
if (await Bun.file(`${project.assetsDir}/styles.css`).exists()) {
|
|
94
|
+
return `${project.assetsDir}/styles.css`;
|
|
95
95
|
}
|
|
96
96
|
return null;
|
|
97
97
|
};
|
|
@@ -138,10 +138,10 @@ const renderStaticSearchPage = async (): Promise<string> => {
|
|
|
138
138
|
};
|
|
139
139
|
|
|
140
140
|
await Bun.write(
|
|
141
|
-
`${project.
|
|
141
|
+
`${project.outputDir}/search/index.html`,
|
|
142
142
|
await renderStaticSearchPage()
|
|
143
143
|
);
|
|
144
|
-
console.log(` generated ${project.
|
|
144
|
+
console.log(` generated ${project.outputDir}/search/index.html`);
|
|
145
145
|
|
|
146
146
|
for (const file of contentFiles) {
|
|
147
147
|
const filePath = `${project.contentDir}/${file}`;
|
|
@@ -160,11 +160,11 @@ for (const file of contentFiles) {
|
|
|
160
160
|
|
|
161
161
|
let outPath: string;
|
|
162
162
|
if (slug === "index") {
|
|
163
|
-
outPath = `${project.
|
|
163
|
+
outPath = `${project.outputDir}/index.html`;
|
|
164
164
|
} else if (slug === "404") {
|
|
165
|
-
outPath = `${project.
|
|
165
|
+
outPath = `${project.outputDir}/404.html`;
|
|
166
166
|
} else {
|
|
167
|
-
outPath = `${project.
|
|
167
|
+
outPath = `${project.outputDir}/${slug}/index.html`;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
await Bun.write(outPath, html);
|
|
@@ -172,24 +172,26 @@ for (const file of contentFiles) {
|
|
|
172
172
|
await writeMarkdownOutputs(slug, markdown);
|
|
173
173
|
|
|
174
174
|
if (slug === "404") {
|
|
175
|
-
const nested404Path = `${project.
|
|
175
|
+
const nested404Path = `${project.outputDir}/404/index.html`;
|
|
176
176
|
await Bun.write(nested404Path, html);
|
|
177
177
|
console.log(` ${filePath} -> ${nested404Path}`);
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
const llmsTxt = await generateLlmsTxt();
|
|
182
|
-
await Bun.write(`${project.
|
|
183
|
-
console.log(` generated ${project.
|
|
182
|
+
await Bun.write(`${project.outputDir}/llms.txt`, llmsTxt);
|
|
183
|
+
console.log(` generated ${project.outputDir}/llms.txt`);
|
|
184
184
|
|
|
185
185
|
const searchIndex = await generateSearchIndexFromContent({ siteConfig });
|
|
186
186
|
await Bun.write(
|
|
187
|
-
`${project.
|
|
187
|
+
`${project.outputDir}/search-index.json`,
|
|
188
188
|
JSON.stringify(searchIndex)
|
|
189
189
|
);
|
|
190
|
-
const searchIndexBytes = Bun.file(
|
|
190
|
+
const searchIndexBytes = Bun.file(
|
|
191
|
+
`${project.outputDir}/search-index.json`
|
|
192
|
+
).size;
|
|
191
193
|
console.log(
|
|
192
|
-
` generated
|
|
194
|
+
` generated public/search-index.json (${(searchIndexBytes / (1024 * 1024)).toFixed(2)} MB)`
|
|
193
195
|
);
|
|
194
196
|
if (searchIndexBytes > MAX_INDEX_BYTES) {
|
|
195
197
|
console.warn(
|
|
@@ -200,19 +202,19 @@ if (searchIndexBytes > MAX_INDEX_BYTES) {
|
|
|
200
202
|
if (siteConfig.baseUrl) {
|
|
201
203
|
const sitemapPages = await collectSitemapPagesFromContent();
|
|
202
204
|
await Bun.write(
|
|
203
|
-
`${project.
|
|
205
|
+
`${project.outputDir}/sitemap.xml`,
|
|
204
206
|
generateSitemapXml(sitemapPages, siteConfig.baseUrl)
|
|
205
207
|
);
|
|
206
|
-
console.log(` generated ${project.
|
|
208
|
+
console.log(` generated ${project.outputDir}/sitemap.xml`);
|
|
207
209
|
|
|
208
210
|
await Bun.write(
|
|
209
|
-
`${project.
|
|
211
|
+
`${project.outputDir}/robots.txt`,
|
|
210
212
|
generateRobotsTxt(siteConfig.baseUrl)
|
|
211
213
|
);
|
|
212
|
-
console.log(` generated ${project.
|
|
214
|
+
console.log(` generated ${project.outputDir}/robots.txt`);
|
|
213
215
|
} else {
|
|
214
216
|
console.log(
|
|
215
|
-
"Warning: site
|
|
217
|
+
"Warning: site.jsonc missing baseUrl; skipping sitemap.xml and robots.txt."
|
|
216
218
|
);
|
|
217
219
|
}
|
|
218
220
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { compileRuntimeAssetsOnce } from "../runtime-assets";
|
|
2
2
|
|
|
3
3
|
const findTailwindInput = async (): Promise<string> => {
|
|
4
|
-
const path = "
|
|
4
|
+
const path = "styles/tailwind.css";
|
|
5
5
|
if (await Bun.file(path).exists()) {
|
|
6
6
|
return path;
|
|
7
7
|
}
|
|
8
8
|
throw new Error(
|
|
9
|
-
"Could not find Tailwind input. Expected
|
|
9
|
+
"Could not find Tailwind input. Expected styles/tailwind.css."
|
|
10
10
|
);
|
|
11
11
|
};
|
|
12
12
|
|
|
@@ -29,7 +29,7 @@ export const buildCommand = async (): Promise<number> => {
|
|
|
29
29
|
"-i",
|
|
30
30
|
tailwindInput,
|
|
31
31
|
"-o",
|
|
32
|
-
"
|
|
32
|
+
"public/styles.css",
|
|
33
33
|
"--minify",
|
|
34
34
|
],
|
|
35
35
|
{ stderr: "inherit", stdout: "inherit" }
|
|
@@ -8,8 +8,7 @@ const TEMPLATE_UI_DIR = joinPath(
|
|
|
8
8
|
"..",
|
|
9
9
|
"templates",
|
|
10
10
|
"default",
|
|
11
|
-
"
|
|
12
|
-
"code",
|
|
11
|
+
"src",
|
|
13
12
|
"ui"
|
|
14
13
|
);
|
|
15
14
|
const TEMPLATE_RUNTIME_DIR = joinPath(
|
|
@@ -19,14 +18,13 @@ const TEMPLATE_RUNTIME_DIR = joinPath(
|
|
|
19
18
|
"..",
|
|
20
19
|
"templates",
|
|
21
20
|
"default",
|
|
22
|
-
"
|
|
23
|
-
"code",
|
|
21
|
+
"src",
|
|
24
22
|
"runtime"
|
|
25
23
|
);
|
|
26
24
|
|
|
27
|
-
const SITE_UI_DIR = joinPath("
|
|
28
|
-
const SITE_RUNTIME_DIR = joinPath("
|
|
29
|
-
const SITE_CONFIG_PATH =
|
|
25
|
+
const SITE_UI_DIR = joinPath("src", "ui");
|
|
26
|
+
const SITE_RUNTIME_DIR = joinPath("src", "runtime");
|
|
27
|
+
const SITE_CONFIG_PATH = "site.jsonc";
|
|
30
28
|
|
|
31
29
|
const CLIENT_PARTS = [
|
|
32
30
|
"layout",
|
|
@@ -144,7 +142,7 @@ const parseClientArgs = (positionals: string[]): ParsedClientArgs => {
|
|
|
144
142
|
const ensureSiteLayout = async (): Promise<void> => {
|
|
145
143
|
if (!(await Bun.file(SITE_CONFIG_PATH).exists())) {
|
|
146
144
|
throw new Error(
|
|
147
|
-
`Could not find ${SITE_CONFIG_PATH}. Run this command from an idcmd
|
|
145
|
+
`Could not find ${SITE_CONFIG_PATH}. Run this command from an idcmd project root.`
|
|
148
146
|
);
|
|
149
147
|
}
|
|
150
148
|
};
|
|
@@ -14,23 +14,23 @@ const warnIfVercelMisconfigured = async (): Promise<void> => {
|
|
|
14
14
|
if (!raw) {
|
|
15
15
|
// eslint-disable-next-line no-console
|
|
16
16
|
console.warn(
|
|
17
|
-
"Warning: vercel.json not found. Vercel static deploy expects
|
|
17
|
+
"Warning: vercel.json not found. Vercel static deploy expects public/ output."
|
|
18
18
|
);
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const record = raw as Record<string, unknown>;
|
|
23
23
|
const out = record.outputDirectory;
|
|
24
|
-
if (out !== "
|
|
24
|
+
if (out !== "public") {
|
|
25
25
|
// eslint-disable-next-line no-console
|
|
26
26
|
console.warn(
|
|
27
|
-
`Warning: vercel.json outputDirectory is not "
|
|
27
|
+
`Warning: vercel.json outputDirectory is not "public" (got ${JSON.stringify(out)}).`
|
|
28
28
|
);
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const warnIfBaseUrlMissing = async (): Promise<void> => {
|
|
33
|
-
const file = Bun.file("site
|
|
33
|
+
const file = Bun.file("site.jsonc");
|
|
34
34
|
if (!(await file.exists())) {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
@@ -42,13 +42,13 @@ const warnIfBaseUrlMissing = async (): Promise<void> => {
|
|
|
42
42
|
if (!cfg.baseUrl) {
|
|
43
43
|
// eslint-disable-next-line no-console
|
|
44
44
|
console.warn(
|
|
45
|
-
"Warning: site
|
|
45
|
+
"Warning: site.jsonc missing baseUrl; sitemap.xml and robots.txt will be skipped."
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
} catch (error) {
|
|
49
49
|
const message = error instanceof Error ? error.message : String(error);
|
|
50
50
|
// eslint-disable-next-line no-console
|
|
51
|
-
console.warn(`Warning: Failed to parse site
|
|
51
|
+
console.warn(`Warning: Failed to parse site.jsonc: ${message}`);
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
|
|
@@ -76,7 +76,7 @@ const printDeployInstructions = (): void => {
|
|
|
76
76
|
// eslint-disable-next-line no-console
|
|
77
77
|
console.log(" 2. Import the repo in Vercel");
|
|
78
78
|
// eslint-disable-next-line no-console
|
|
79
|
-
console.log(" 3. Vercel will run `bun run build` and serve `
|
|
79
|
+
console.log(" 3. Vercel will run `bun run build` and serve `public/`");
|
|
80
80
|
// eslint-disable-next-line no-console
|
|
81
81
|
console.log("");
|
|
82
82
|
};
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -11,16 +11,16 @@ export interface DevFlags {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const findTailwindInput = async (): Promise<string> => {
|
|
14
|
-
const path = "
|
|
14
|
+
const path = "styles/tailwind.css";
|
|
15
15
|
if (await Bun.file(path).exists()) {
|
|
16
16
|
return path;
|
|
17
17
|
}
|
|
18
18
|
throw new Error(
|
|
19
|
-
"Could not find Tailwind input. Expected
|
|
19
|
+
"Could not find Tailwind input. Expected styles/tailwind.css."
|
|
20
20
|
);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
const resolveTailwindOutput = (): string => "
|
|
23
|
+
const resolveTailwindOutput = (): string => "public/styles.css";
|
|
24
24
|
|
|
25
25
|
const idcmdServerEntry = (): string =>
|
|
26
26
|
// `src/server.ts` lives two levels up from `src/cli/commands/*`.
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -200,7 +200,7 @@ const applySubstitutions = async (args: {
|
|
|
200
200
|
siteName: string;
|
|
201
201
|
targetDir: string;
|
|
202
202
|
}): Promise<void> => {
|
|
203
|
-
await replaceInFile(joinPath(args.targetDir, "site
|
|
203
|
+
await replaceInFile(joinPath(args.targetDir, "site.jsonc"), (text) =>
|
|
204
204
|
fillSiteJsonc({
|
|
205
205
|
baseUrl: args.baseUrl,
|
|
206
206
|
description: args.description,
|
|
@@ -7,7 +7,7 @@ const stripLeadingSlash = (pathname: string): string =>
|
|
|
7
7
|
pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
8
8
|
|
|
9
9
|
const tryServeFile = async (relativePath: string): Promise<Response | null> => {
|
|
10
|
-
const file = Bun.file(`
|
|
10
|
+
const file = Bun.file(`public/${stripLeadingSlash(relativePath)}`);
|
|
11
11
|
if (!(await file.exists())) {
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
@@ -37,9 +37,9 @@ const serveHtml = async (pathname: string): Promise<Response> => {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
export const previewCommand = async (port: number): Promise<number> => {
|
|
40
|
-
const exists = await Bun.file("
|
|
40
|
+
const exists = await Bun.file("public/index.html").exists();
|
|
41
41
|
if (!exists) {
|
|
42
|
-
throw new Error("
|
|
42
|
+
throw new Error("public/ not found. Run `idcmd build` first.");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const server = Bun.serve({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { joinPath } from "./path";
|
|
2
2
|
|
|
3
|
-
const RUNTIME_SOURCE_DIR = joinPath("
|
|
4
|
-
const RUNTIME_OUTPUT_DIR = joinPath("
|
|
3
|
+
const RUNTIME_SOURCE_DIR = joinPath("src", "runtime");
|
|
4
|
+
const RUNTIME_OUTPUT_DIR = joinPath("public", "_idcmd");
|
|
5
5
|
|
|
6
6
|
const RUNTIME_ENTRY_FILES = [
|
|
7
7
|
"live-reload.ts",
|
package/src/content/paths.ts
CHANGED
|
@@ -21,7 +21,7 @@ export const slugFromContentFile = (file: string): string => {
|
|
|
21
21
|
export const scanContentFiles =
|
|
22
22
|
async function* scanContentFiles(): AsyncGenerator<string> {
|
|
23
23
|
const { contentDir } = await getProjectPaths();
|
|
24
|
-
// `
|
|
24
|
+
// `content/<slug>.md`
|
|
25
25
|
for await (const file of flatContentGlob.scan(contentDir)) {
|
|
26
26
|
yield file;
|
|
27
27
|
}
|
package/src/project/paths.ts
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
|
-
const
|
|
2
|
-
const DEFAULT_DIST_DIR = "dist";
|
|
1
|
+
const DEFAULT_OUTPUT_DIR = "public";
|
|
3
2
|
const ASSET_PREFIX = "/_idcmd" as const;
|
|
4
3
|
const CONTENT_DIR = "content";
|
|
5
|
-
const
|
|
4
|
+
const ASSETS_DIR = "assets";
|
|
6
5
|
const ICONS_DIR = "assets/icons";
|
|
7
|
-
const ROUTES_DIR = "
|
|
6
|
+
const ROUTES_DIR = "src/routes";
|
|
8
7
|
const SITE_CONFIG_FILE = "site.jsonc";
|
|
9
8
|
|
|
10
9
|
export interface ProjectPaths {
|
|
11
10
|
assetPrefix: typeof ASSET_PREFIX;
|
|
12
11
|
contentDir: string;
|
|
13
|
-
|
|
12
|
+
outputDir: string;
|
|
14
13
|
iconsDir: string;
|
|
15
|
-
|
|
14
|
+
assetsDir: string;
|
|
16
15
|
routesDir: string;
|
|
17
16
|
siteConfigPath: string;
|
|
18
|
-
siteDir: string | null;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
export interface ResolveProjectPathsOptions {
|
|
22
20
|
cwd?: string;
|
|
23
|
-
|
|
24
|
-
siteDir?: string;
|
|
21
|
+
outputDir?: string;
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
const trimTrailingSlash = (value: string): string =>
|
|
@@ -44,29 +41,23 @@ const joinPath = (...parts: string[]): string => {
|
|
|
44
41
|
|
|
45
42
|
const buildPaths = (args: {
|
|
46
43
|
cwd: string;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
routesDir: joinPath(siteRoot, ROUTES_DIR),
|
|
58
|
-
siteConfigPath: joinPath(siteRoot, SITE_CONFIG_FILE),
|
|
59
|
-
siteDir: siteRoot,
|
|
60
|
-
};
|
|
61
|
-
};
|
|
44
|
+
outputDirName: string;
|
|
45
|
+
}): ProjectPaths => ({
|
|
46
|
+
assetPrefix: ASSET_PREFIX,
|
|
47
|
+
assetsDir: joinPath(args.cwd, ASSETS_DIR),
|
|
48
|
+
contentDir: joinPath(args.cwd, CONTENT_DIR),
|
|
49
|
+
iconsDir: joinPath(args.cwd, ICONS_DIR),
|
|
50
|
+
outputDir: joinPath(args.cwd, args.outputDirName),
|
|
51
|
+
routesDir: joinPath(args.cwd, ROUTES_DIR),
|
|
52
|
+
siteConfigPath: joinPath(args.cwd, SITE_CONFIG_FILE),
|
|
53
|
+
});
|
|
62
54
|
|
|
63
55
|
export const resolveProjectPaths = (
|
|
64
56
|
options: ResolveProjectPathsOptions = {}
|
|
65
57
|
): Promise<ProjectPaths> => {
|
|
66
58
|
const cwd = trimTrailingSlash(options.cwd ?? process.cwd());
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
return Promise.resolve(buildPaths({ cwd, distDirName, siteDirName }));
|
|
59
|
+
const outputDirName = options.outputDir ?? DEFAULT_OUTPUT_DIR;
|
|
60
|
+
return Promise.resolve(buildPaths({ cwd, outputDirName }));
|
|
70
61
|
};
|
|
71
62
|
|
|
72
63
|
let cached: Promise<ProjectPaths> | null = null;
|
|
@@ -2,7 +2,7 @@ import type { RenderLayout } from "./layout";
|
|
|
2
2
|
|
|
3
3
|
import { renderLayout as defaultRenderLayout } from "./layout";
|
|
4
4
|
|
|
5
|
-
const USER_LAYOUT_PATH = "
|
|
5
|
+
const USER_LAYOUT_PATH = "src/ui/layout.tsx";
|
|
6
6
|
|
|
7
7
|
const loadUserLayout = async (
|
|
8
8
|
filePath: string
|
|
@@ -2,7 +2,7 @@ import type { RightRailComponent } from "./right-rail";
|
|
|
2
2
|
|
|
3
3
|
import { RightRail as defaultRightRail } from "./right-rail";
|
|
4
4
|
|
|
5
|
-
const USER_RIGHT_RAIL_PATH = "
|
|
5
|
+
const USER_RIGHT_RAIL_PATH = "src/ui/right-rail.tsx";
|
|
6
6
|
|
|
7
7
|
const loadUserRightRail = async (
|
|
8
8
|
filePath: string
|
package/src/search/index.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface SearchIndexV1 {
|
|
|
26
26
|
documents: SearchIndexDocumentV1[];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const SEARCH_INDEX_PATH = "
|
|
29
|
+
const SEARCH_INDEX_PATH = "public/search-index.json";
|
|
30
30
|
const MIN_QUERY_TOKEN_LENGTH = 2;
|
|
31
31
|
const DEFAULT_BODY_MAX_CHARS = 2000;
|
|
32
32
|
|
|
@@ -2,7 +2,7 @@ import type { RenderSearchPageContent } from "./page";
|
|
|
2
2
|
|
|
3
3
|
import { renderSearchPageContent as defaultRenderSearchPageContent } from "./page";
|
|
4
4
|
|
|
5
|
-
const USER_SEARCH_PAGE_PATH = "
|
|
5
|
+
const USER_SEARCH_PAGE_PATH = "src/ui/search-page.tsx";
|
|
6
6
|
|
|
7
7
|
const loadUserSearchPage = async (
|
|
8
8
|
filePath: string
|
package/src/seo/server.ts
CHANGED
|
@@ -7,12 +7,12 @@ import {
|
|
|
7
7
|
} from "./files";
|
|
8
8
|
|
|
9
9
|
export interface SeoHandlerEnv {
|
|
10
|
-
|
|
10
|
+
outputDir: string;
|
|
11
11
|
isDev: boolean;
|
|
12
12
|
staticCacheHeaders: HeadersInit;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const tryServeOutputFile = async (
|
|
16
16
|
filePath: string,
|
|
17
17
|
contentType: string,
|
|
18
18
|
env: SeoHandlerEnv
|
|
@@ -42,8 +42,8 @@ export const handleRobotsTxt = async (
|
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const served = await
|
|
46
|
-
`${env.
|
|
45
|
+
const served = await tryServeOutputFile(
|
|
46
|
+
`${env.outputDir}/robots.txt`,
|
|
47
47
|
"text/plain; charset=utf-8",
|
|
48
48
|
env
|
|
49
49
|
);
|
|
@@ -75,8 +75,8 @@ export const handleSitemapXml = async (
|
|
|
75
75
|
return undefined;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const served = await
|
|
79
|
-
`${env.
|
|
78
|
+
const served = await tryServeOutputFile(
|
|
79
|
+
`${env.outputDir}/sitemap.xml`,
|
|
80
80
|
"application/xml; charset=utf-8",
|
|
81
81
|
env
|
|
82
82
|
);
|
|
@@ -59,7 +59,7 @@ export const createLiveReload = (env: LiveReloadEnv): LiveReloadController => {
|
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
console.log("Watching
|
|
62
|
+
console.log("Watching content/ for changes...");
|
|
63
63
|
let snapshot = await getContentSnapshot();
|
|
64
64
|
|
|
65
65
|
const poll = async (): Promise<void> => {
|
package/src/server/static.ts
CHANGED
|
@@ -13,9 +13,9 @@ const mimeTypes: Record<string, string> = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export interface ServeStaticEnv {
|
|
16
|
-
|
|
16
|
+
outputDir: string;
|
|
17
17
|
isDev: boolean;
|
|
18
|
-
|
|
18
|
+
assetsDir: string;
|
|
19
19
|
staticCacheHeaders: HeadersInit;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -46,7 +46,7 @@ export const serveStaticFile = async (
|
|
|
46
46
|
pathname: string,
|
|
47
47
|
env: ServeStaticEnv
|
|
48
48
|
): Promise<Response | null> => {
|
|
49
|
-
const roots = [env.
|
|
49
|
+
const roots = [env.outputDir, env.assetsDir];
|
|
50
50
|
|
|
51
51
|
for (const root of roots) {
|
|
52
52
|
const served = await tryServeFileFromRoot(root, pathname, env);
|