idcmd 0.0.6 → 0.0.7
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 +10 -9
- package/package.json +1 -1
- package/src/build.ts +2 -2
- package/src/cli/commands/build.ts +4 -7
- package/src/cli/commands/client.ts +22 -11
- package/src/cli/commands/dev.ts +6 -12
- package/src/cli/runtime-assets.ts +2 -5
- package/src/cli.ts +0 -0
- package/src/content/icons.ts +1 -1
- package/src/content/paths.ts +1 -1
- package/src/project/paths.ts +26 -30
- package/src/render/layout-loader.ts +1 -1
- package/src/render/right-rail-loader.ts +1 -1
- package/src/search/search-page-loader.ts +1 -1
- package/src/server/live-reload.ts +2 -6
- package/src/server/static.ts +1 -1
- package/src/server.ts +0 -1
- package/src/site/config.ts +2 -10
- package/templates/default/README.md +12 -9
- package/templates/default/scripts/check-internal.ts +8 -8
- package/templates/default/scripts/check.ts +15 -1
- package/templates/default/scripts/smoke.ts +32 -2
- package/templates/default/site/{server → code}/server.ts +1 -1
- /package/templates/default/site/{public → assets}/anthropic-white.svg +0 -0
- /package/templates/default/site/{public → assets}/favicon.svg +0 -0
- /package/templates/default/site/{icons → assets/icons}/file.svg +0 -0
- /package/templates/default/site/{icons → assets/icons}/home.svg +0 -0
- /package/templates/default/site/{icons → assets/icons}/info.svg +0 -0
- /package/templates/default/site/{public → assets}/openai-white.svg +0 -0
- /package/templates/default/site/{server → code}/routes/api/hello.ts +0 -0
- /package/templates/default/site/{client → code}/runtime/live-reload.ts +0 -0
- /package/templates/default/site/{client → code}/runtime/llm-menu.ts +0 -0
- /package/templates/default/site/{client → code}/runtime/nav-prefetch.ts +0 -0
- /package/templates/default/site/{client → code}/runtime/right-rail-scrollspy.ts +0 -0
- /package/templates/default/site/{client → code/ui}/layout.tsx +0 -0
- /package/templates/default/site/{client → code/ui}/right-rail.tsx +0 -0
- /package/templates/default/site/{client → code/ui}/search-page.tsx +0 -0
package/README.md
CHANGED
|
@@ -19,18 +19,19 @@ idcmd dev # tailwind watch + SSR dev server
|
|
|
19
19
|
idcmd build # static dist/
|
|
20
20
|
idcmd preview # serve dist/ locally
|
|
21
21
|
idcmd deploy # build + validate Vercel static deploy config
|
|
22
|
-
idcmd client ... # add/update local site/
|
|
22
|
+
idcmd client ... # add/update local site/code implementations
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Layout (V1)
|
|
26
26
|
|
|
27
27
|
- `site/content/<slug>.md` -> `/<slug>/` (`index.md` -> `/`)
|
|
28
|
-
- `site/
|
|
29
|
-
- `site/
|
|
30
|
-
- `site/
|
|
31
|
-
- `site/
|
|
32
|
-
- `site/
|
|
28
|
+
- `site/code/ui/*` is local UI source code (you own and edit these files)
|
|
29
|
+
- `site/code/runtime/*.ts` is local browser runtime code (compiled to `dist/_idcmd/*.js`)
|
|
30
|
+
- `site/code/routes/**` file-based server routes (dev/server-host only)
|
|
31
|
+
- `site/styles/tailwind.css` -> `dist/styles.css`
|
|
32
|
+
- `site/assets/` static assets
|
|
33
33
|
- `site/site.jsonc` site config
|
|
34
|
+
- `dist/` generated output (gitignored)
|
|
34
35
|
|
|
35
36
|
## Syncing Local Client Code
|
|
36
37
|
|
|
@@ -44,7 +45,7 @@ idcmd client update runtime --yes
|
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
`add` creates missing files. `update` overwrites changed files and requires `--yes` unless `--dry-run` is used.
|
|
47
|
-
Runtime files in `site/
|
|
48
|
+
Runtime files in `site/code/runtime/` are compiled automatically by `idcmd dev` and `idcmd build`.
|
|
48
49
|
|
|
49
50
|
## Example: Add A Page
|
|
50
51
|
|
|
@@ -67,7 +68,7 @@ It renders at `/hello/`.
|
|
|
67
68
|
|
|
68
69
|
## Custom Server Routes (V1)
|
|
69
70
|
|
|
70
|
-
Add `site/
|
|
71
|
+
Add `site/code/routes/api/hello.ts`:
|
|
71
72
|
|
|
72
73
|
```ts
|
|
73
74
|
export const GET = (): Response => Response.json({ ok: true });
|
|
@@ -93,7 +94,7 @@ It responds at `/api/hello`.
|
|
|
93
94
|
|
|
94
95
|
### Slug and path rules
|
|
95
96
|
|
|
96
|
-
- Content lives at `site/content/<slug>.md
|
|
97
|
+
- Content lives at `site/content/<slug>.md`.
|
|
97
98
|
- `slug="index"` is the home page.
|
|
98
99
|
- Canonical HTML paths are `/` for index and `/<slug>/` otherwise.
|
|
99
100
|
- Markdown download paths exist in two forms:
|
package/package.json
CHANGED
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 `content/` (`content/<slug>.md`).
|
|
23
|
+
// Find all content files in `site/content/` (`site/content/<slug>.md`).
|
|
24
24
|
const contentFiles: string[] = [];
|
|
25
25
|
|
|
26
26
|
const buildStart = performance.now();
|
|
@@ -212,7 +212,7 @@ if (siteConfig.baseUrl) {
|
|
|
212
212
|
console.log(` generated ${project.distDir}/robots.txt`);
|
|
213
213
|
} else {
|
|
214
214
|
console.log(
|
|
215
|
-
"Warning: site.jsonc missing baseUrl; skipping sitemap.xml and robots.txt."
|
|
215
|
+
"Warning: site/site.jsonc missing baseUrl; skipping sitemap.xml and robots.txt."
|
|
216
216
|
);
|
|
217
217
|
}
|
|
218
218
|
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { compileRuntimeAssetsOnce } from "../runtime-assets";
|
|
2
2
|
|
|
3
3
|
const findTailwindInput = async (): Promise<string> => {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (await Bun.file(path).exists()) {
|
|
8
|
-
return path;
|
|
9
|
-
}
|
|
4
|
+
const path = "site/styles/tailwind.css";
|
|
5
|
+
if (await Bun.file(path).exists()) {
|
|
6
|
+
return path;
|
|
10
7
|
}
|
|
11
8
|
throw new Error(
|
|
12
|
-
"Could not find Tailwind input. Expected site/styles/tailwind.css
|
|
9
|
+
"Could not find Tailwind input. Expected site/styles/tailwind.css."
|
|
13
10
|
);
|
|
14
11
|
};
|
|
15
12
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ensureDir } from "../fs";
|
|
2
2
|
import { dirname, joinPath } from "../path";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const TEMPLATE_UI_DIR = joinPath(
|
|
5
5
|
import.meta.dir,
|
|
6
6
|
"..",
|
|
7
7
|
"..",
|
|
@@ -9,12 +9,23 @@ const TEMPLATE_CLIENT_DIR = joinPath(
|
|
|
9
9
|
"templates",
|
|
10
10
|
"default",
|
|
11
11
|
"site",
|
|
12
|
-
"
|
|
12
|
+
"code",
|
|
13
|
+
"ui"
|
|
14
|
+
);
|
|
15
|
+
const TEMPLATE_RUNTIME_DIR = joinPath(
|
|
16
|
+
import.meta.dir,
|
|
17
|
+
"..",
|
|
18
|
+
"..",
|
|
19
|
+
"..",
|
|
20
|
+
"templates",
|
|
21
|
+
"default",
|
|
22
|
+
"site",
|
|
23
|
+
"code",
|
|
24
|
+
"runtime"
|
|
13
25
|
);
|
|
14
|
-
const TEMPLATE_RUNTIME_DIR = joinPath(TEMPLATE_CLIENT_DIR, "runtime");
|
|
15
26
|
|
|
16
|
-
const
|
|
17
|
-
const SITE_RUNTIME_DIR = joinPath(
|
|
27
|
+
const SITE_UI_DIR = joinPath("site", "code", "ui");
|
|
28
|
+
const SITE_RUNTIME_DIR = joinPath("site", "code", "runtime");
|
|
18
29
|
const SITE_CONFIG_PATH = joinPath("site", "site.jsonc");
|
|
19
30
|
|
|
20
31
|
const CLIENT_PARTS = [
|
|
@@ -63,8 +74,8 @@ const getFileSpecsForPart = (part: ClientPart): ClientFileSpec[] => {
|
|
|
63
74
|
return [
|
|
64
75
|
{
|
|
65
76
|
fileName: "layout.tsx",
|
|
66
|
-
targetPath: joinPath(
|
|
67
|
-
templatePath: joinPath(
|
|
77
|
+
targetPath: joinPath(SITE_UI_DIR, "layout.tsx"),
|
|
78
|
+
templatePath: joinPath(TEMPLATE_UI_DIR, "layout.tsx"),
|
|
68
79
|
},
|
|
69
80
|
];
|
|
70
81
|
}
|
|
@@ -73,8 +84,8 @@ const getFileSpecsForPart = (part: ClientPart): ClientFileSpec[] => {
|
|
|
73
84
|
return [
|
|
74
85
|
{
|
|
75
86
|
fileName: "right-rail.tsx",
|
|
76
|
-
targetPath: joinPath(
|
|
77
|
-
templatePath: joinPath(
|
|
87
|
+
targetPath: joinPath(SITE_UI_DIR, "right-rail.tsx"),
|
|
88
|
+
templatePath: joinPath(TEMPLATE_UI_DIR, "right-rail.tsx"),
|
|
78
89
|
},
|
|
79
90
|
];
|
|
80
91
|
}
|
|
@@ -83,8 +94,8 @@ const getFileSpecsForPart = (part: ClientPart): ClientFileSpec[] => {
|
|
|
83
94
|
return [
|
|
84
95
|
{
|
|
85
96
|
fileName: "search-page.tsx",
|
|
86
|
-
targetPath: joinPath(
|
|
87
|
-
templatePath: joinPath(
|
|
97
|
+
targetPath: joinPath(SITE_UI_DIR, "search-page.tsx"),
|
|
98
|
+
templatePath: joinPath(TEMPLATE_UI_DIR, "search-page.tsx"),
|
|
88
99
|
},
|
|
89
100
|
];
|
|
90
101
|
}
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -11,22 +11,16 @@ export interface DevFlags {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const findTailwindInput = async (): Promise<string> => {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (await Bun.file(path).exists()) {
|
|
18
|
-
return path;
|
|
19
|
-
}
|
|
14
|
+
const path = "site/styles/tailwind.css";
|
|
15
|
+
if (await Bun.file(path).exists()) {
|
|
16
|
+
return path;
|
|
20
17
|
}
|
|
21
18
|
throw new Error(
|
|
22
|
-
"Could not find Tailwind input. Expected site/styles/tailwind.css
|
|
19
|
+
"Could not find Tailwind input. Expected site/styles/tailwind.css."
|
|
23
20
|
);
|
|
24
21
|
};
|
|
25
22
|
|
|
26
|
-
const resolveTailwindOutput =
|
|
27
|
-
const hasSite = await Bun.file("site/site.jsonc").exists();
|
|
28
|
-
return hasSite ? "site/public/styles.css" : "public/styles.css";
|
|
29
|
-
};
|
|
23
|
+
const resolveTailwindOutput = (): string => "dist/styles.css";
|
|
30
24
|
|
|
31
25
|
const idcmdServerEntry = (): string =>
|
|
32
26
|
// `src/server.ts` lives two levels up from `src/cli/commands/*`.
|
|
@@ -135,7 +129,7 @@ export const devCommand = async (flags: DevFlags): Promise<number> => {
|
|
|
135
129
|
}
|
|
136
130
|
|
|
137
131
|
const tailwindInput = await findTailwindInput();
|
|
138
|
-
const tailwindOutput =
|
|
132
|
+
const tailwindOutput = resolveTailwindOutput();
|
|
139
133
|
const cssProc = spawnCssProcess(tailwindInput, tailwindOutput);
|
|
140
134
|
const serverProc = spawnServerProcess(port);
|
|
141
135
|
installDevSignalHandlers({ cssProc, runtimeProc, serverProc });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { joinPath } from "./path";
|
|
2
2
|
|
|
3
|
-
const RUNTIME_SOURCE_DIR = joinPath("site", "
|
|
4
|
-
const RUNTIME_OUTPUT_DIR = joinPath("
|
|
3
|
+
const RUNTIME_SOURCE_DIR = joinPath("site", "code", "runtime");
|
|
4
|
+
const RUNTIME_OUTPUT_DIR = joinPath("dist", "_idcmd");
|
|
5
5
|
|
|
6
6
|
const RUNTIME_ENTRY_FILES = [
|
|
7
7
|
"live-reload.ts",
|
|
@@ -37,17 +37,14 @@ const scanRuntimeEntrypoints = async (
|
|
|
37
37
|
const getRuntimeEntrypoints = async (): Promise<string[]> => {
|
|
38
38
|
const entries = runtimeEntryPaths();
|
|
39
39
|
const { existing, missing } = await scanRuntimeEntrypoints(entries);
|
|
40
|
-
|
|
41
40
|
if (existing.length === 0) {
|
|
42
41
|
return [];
|
|
43
42
|
}
|
|
44
|
-
|
|
45
43
|
if (missing.length > 0) {
|
|
46
44
|
throw new Error(
|
|
47
45
|
`Incomplete runtime scripts in ${RUNTIME_SOURCE_DIR}. Missing: ${missing.join(", ")}`
|
|
48
46
|
);
|
|
49
47
|
}
|
|
50
|
-
|
|
51
48
|
return existing;
|
|
52
49
|
};
|
|
53
50
|
|
package/src/cli.ts
CHANGED
|
File without changes
|
package/src/content/icons.ts
CHANGED
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
|
-
// `content/<slug>.md`
|
|
24
|
+
// `site/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,6 +1,11 @@
|
|
|
1
1
|
const DEFAULT_SITE_DIR = "site";
|
|
2
2
|
const DEFAULT_DIST_DIR = "dist";
|
|
3
3
|
const ASSET_PREFIX = "/_idcmd" as const;
|
|
4
|
+
const CONTENT_DIR = "content";
|
|
5
|
+
const PUBLIC_DIR = "assets";
|
|
6
|
+
const ICONS_DIR = "assets/icons";
|
|
7
|
+
const ROUTES_DIR = "code/routes";
|
|
8
|
+
const SITE_CONFIG_FILE = "site.jsonc";
|
|
4
9
|
|
|
5
10
|
export interface ProjectPaths {
|
|
6
11
|
assetPrefix: typeof ASSET_PREFIX;
|
|
@@ -37,50 +42,41 @@ const joinPath = (...parts: string[]): string => {
|
|
|
37
42
|
return normalized.join("/");
|
|
38
43
|
};
|
|
39
44
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
distDirName: string
|
|
47
|
-
): ProjectPaths => {
|
|
48
|
-
const distDir = joinPath(cwd, distDirName);
|
|
49
|
-
const contentDir = joinPath(rootDir, "content");
|
|
50
|
-
const publicDir = joinPath(rootDir, "public");
|
|
51
|
-
const iconsDir = joinPath(rootDir, "icons");
|
|
52
|
-
const routesDir = joinPath(rootDir, "server", "routes");
|
|
53
|
-
const siteConfigPath = joinPath(rootDir, "site.jsonc");
|
|
54
|
-
|
|
55
|
-
const siteDir = rootDir === cwd ? null : rootDir;
|
|
56
|
-
|
|
45
|
+
const buildPaths = (args: {
|
|
46
|
+
cwd: string;
|
|
47
|
+
distDirName: string;
|
|
48
|
+
siteDirName: string;
|
|
49
|
+
}): ProjectPaths => {
|
|
50
|
+
const siteRoot = joinPath(args.cwd, args.siteDirName);
|
|
57
51
|
return {
|
|
58
52
|
assetPrefix: ASSET_PREFIX,
|
|
59
|
-
contentDir,
|
|
60
|
-
distDir,
|
|
61
|
-
iconsDir,
|
|
62
|
-
publicDir,
|
|
63
|
-
routesDir,
|
|
64
|
-
siteConfigPath,
|
|
65
|
-
siteDir,
|
|
53
|
+
contentDir: joinPath(siteRoot, CONTENT_DIR),
|
|
54
|
+
distDir: joinPath(args.cwd, args.distDirName),
|
|
55
|
+
iconsDir: joinPath(siteRoot, ICONS_DIR),
|
|
56
|
+
publicDir: joinPath(siteRoot, PUBLIC_DIR),
|
|
57
|
+
routesDir: joinPath(siteRoot, ROUTES_DIR),
|
|
58
|
+
siteConfigPath: joinPath(siteRoot, SITE_CONFIG_FILE),
|
|
59
|
+
siteDir: siteRoot,
|
|
66
60
|
};
|
|
67
61
|
};
|
|
68
62
|
|
|
69
|
-
export const resolveProjectPaths =
|
|
63
|
+
export const resolveProjectPaths = (
|
|
70
64
|
options: ResolveProjectPathsOptions = {}
|
|
71
65
|
): Promise<ProjectPaths> => {
|
|
72
66
|
const cwd = trimTrailingSlash(options.cwd ?? process.cwd());
|
|
73
67
|
const distDirName = options.distDir ?? DEFAULT_DIST_DIR;
|
|
74
68
|
const siteDirName = options.siteDir ?? DEFAULT_SITE_DIR;
|
|
75
|
-
|
|
76
|
-
const siteRoot = joinPath(cwd, siteDirName);
|
|
77
|
-
const rootDir = (await hasNewLayout(cwd, siteDirName)) ? siteRoot : cwd;
|
|
78
|
-
return buildPaths(cwd, rootDir, distDirName);
|
|
69
|
+
return Promise.resolve(buildPaths({ cwd, distDirName, siteDirName }));
|
|
79
70
|
};
|
|
80
71
|
|
|
81
72
|
let cached: Promise<ProjectPaths> | null = null;
|
|
73
|
+
let cachedCwd: string | null = null;
|
|
82
74
|
|
|
83
75
|
export const getProjectPaths = (): Promise<ProjectPaths> => {
|
|
84
|
-
|
|
76
|
+
const cwd = trimTrailingSlash(process.cwd());
|
|
77
|
+
if (!cached || cachedCwd !== cwd) {
|
|
78
|
+
cachedCwd = cwd;
|
|
79
|
+
cached = resolveProjectPaths({ cwd });
|
|
80
|
+
}
|
|
85
81
|
return cached;
|
|
86
82
|
};
|
|
@@ -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 = "site/
|
|
5
|
+
const USER_LAYOUT_PATH = "site/code/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 = "site/
|
|
5
|
+
const USER_RIGHT_RAIL_PATH = "site/code/ui/right-rail.tsx";
|
|
6
6
|
|
|
7
7
|
const loadUserRightRail = async (
|
|
8
8
|
filePath: string
|
|
@@ -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 = "site/
|
|
5
|
+
const USER_SEARCH_PAGE_PATH = "site/code/ui/search-page.tsx";
|
|
6
6
|
|
|
7
7
|
const loadUserSearchPage = async (
|
|
8
8
|
filePath: string
|
|
@@ -59,7 +59,7 @@ export const createLiveReload = (env: LiveReloadEnv): LiveReloadController => {
|
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
console.log("Watching content/ for changes...");
|
|
62
|
+
console.log("Watching site/content/ for changes...");
|
|
63
63
|
let snapshot = await getContentSnapshot();
|
|
64
64
|
|
|
65
65
|
const poll = async (): Promise<void> => {
|
|
@@ -85,11 +85,7 @@ export const createLiveReload = (env: LiveReloadEnv): LiveReloadController => {
|
|
|
85
85
|
server: ServerInstance,
|
|
86
86
|
pathname: string
|
|
87
87
|
): "handled" | Response | undefined => {
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
!env.isDev ||
|
|
91
|
-
(pathname !== env.websocketPath && pathname !== "/__live-reload")
|
|
92
|
-
) {
|
|
88
|
+
if (!env.isDev || pathname !== env.websocketPath) {
|
|
93
89
|
return undefined;
|
|
94
90
|
}
|
|
95
91
|
|
package/src/server/static.ts
CHANGED
|
@@ -46,7 +46,7 @@ export const serveStaticFile = async (
|
|
|
46
46
|
pathname: string,
|
|
47
47
|
env: ServeStaticEnv
|
|
48
48
|
): Promise<Response | null> => {
|
|
49
|
-
const roots =
|
|
49
|
+
const roots = [env.distDir, env.publicDir];
|
|
50
50
|
|
|
51
51
|
for (const root of roots) {
|
|
52
52
|
const served = await tryServeFileFromRoot(root, pathname, env);
|
package/src/server.ts
CHANGED
package/src/site/config.ts
CHANGED
|
@@ -91,8 +91,7 @@ export interface ResolvedRightRailConfig {
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const
|
|
95
|
-
const NEW_SITE_CONFIG_PATH = "site/site.jsonc";
|
|
94
|
+
const SITE_CONFIG_PATH = "site/site.jsonc";
|
|
96
95
|
const LOCALHOST_HOSTNAMES = new Set(["localhost", "127.0.0.1", "::1"]);
|
|
97
96
|
|
|
98
97
|
const DEFAULT_RIGHT_RAIL_CONFIG: ResolvedRightRailConfig = {
|
|
@@ -220,15 +219,8 @@ const parseSiteConfigUnknown = (
|
|
|
220
219
|
}
|
|
221
220
|
};
|
|
222
221
|
|
|
223
|
-
const resolveSiteConfigPath = async (): Promise<string> => {
|
|
224
|
-
if (await Bun.file(NEW_SITE_CONFIG_PATH).exists()) {
|
|
225
|
-
return NEW_SITE_CONFIG_PATH;
|
|
226
|
-
}
|
|
227
|
-
return LEGACY_SITE_CONFIG_PATH;
|
|
228
|
-
};
|
|
229
|
-
|
|
230
222
|
export const loadSiteConfig = async (): Promise<SiteConfig> => {
|
|
231
|
-
const configPath =
|
|
223
|
+
const configPath = SITE_CONFIG_PATH;
|
|
232
224
|
const file = Bun.file(configPath);
|
|
233
225
|
if (!(await file.exists())) {
|
|
234
226
|
return DEFAULT_SITE_CONFIG;
|
|
@@ -20,13 +20,16 @@ bun run smoke
|
|
|
20
20
|
|
|
21
21
|
## Layout
|
|
22
22
|
|
|
23
|
-
- `site/content/` markdown pages (`index.md` -> `/`, `about.md` -> `/about/`)
|
|
24
|
-
- `site/
|
|
25
|
-
- `site/
|
|
26
|
-
- `site/
|
|
27
|
-
- `site/
|
|
28
|
-
- `site/
|
|
29
|
-
- `site/site.jsonc`
|
|
23
|
+
- Content: `site/content/` markdown pages (`index.md` -> `/`, `about.md` -> `/about/`)
|
|
24
|
+
- Code: `site/code/ui/` (`layout.tsx`, `right-rail.tsx`, `search-page.tsx`)
|
|
25
|
+
- Code: `site/code/runtime/` browser runtime TS (`*_idcmd` scripts compile from here)
|
|
26
|
+
- Code: `site/code/routes/` file-based server routes (dev/server-host only)
|
|
27
|
+
- Assets: `site/assets/` static files you own (icons, images, favicon, etc.)
|
|
28
|
+
- Styles source: `site/styles/tailwind.css`
|
|
29
|
+
- Config: `site/site.jsonc`
|
|
30
|
+
- Generated output: `dist/` (`dist/styles.css`, `dist/_idcmd/*.js`, built pages)
|
|
31
|
+
|
|
32
|
+
The mental model is simple: edit `site/content` and `site/code`, treat `dist/` as generated output.
|
|
30
33
|
|
|
31
34
|
## Sync Local Client Files
|
|
32
35
|
|
|
@@ -37,8 +40,8 @@ idcmd client update layout --yes
|
|
|
37
40
|
idcmd client update runtime --yes
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
These commands copy the latest baseline implementations from `idcmd` into `site/
|
|
41
|
-
Runtime files in `site/
|
|
43
|
+
These commands copy the latest baseline implementations from `idcmd` into `site/code/ui/` and `site/code/runtime/`.
|
|
44
|
+
Runtime files in `site/code/runtime/` are compiled automatically by `idcmd dev` and `idcmd build`.
|
|
42
45
|
|
|
43
46
|
## Deploy (Vercel static)
|
|
44
47
|
|
|
@@ -11,16 +11,16 @@ const checks: InternalCheck[] = [
|
|
|
11
11
|
run: () => fileExists("package.json"),
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
description: "site config must exist (site/site.jsonc
|
|
15
|
-
run:
|
|
16
|
-
(await fileExists("site/site.jsonc")) || (await fileExists("site.jsonc")),
|
|
14
|
+
description: "site config must exist (site/site.jsonc)",
|
|
15
|
+
run: () => fileExists("site/site.jsonc"),
|
|
17
16
|
},
|
|
18
17
|
{
|
|
19
|
-
description:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
description: "tailwind input must exist (site/styles/tailwind.css)",
|
|
19
|
+
run: () => fileExists("site/styles/tailwind.css"),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
description: "site code UI entry must exist (site/code/ui/layout.tsx)",
|
|
23
|
+
run: () => fileExists("site/code/ui/layout.tsx"),
|
|
24
24
|
},
|
|
25
25
|
];
|
|
26
26
|
|
|
@@ -28,6 +28,20 @@ const LINT_CONFIG_ERROR =
|
|
|
28
28
|
"No linter configuration found. Run `bun x ultracite init` once in this project.";
|
|
29
29
|
const NO_TESTS_MESSAGE = "No tests found; skipping test step.";
|
|
30
30
|
const MAX_OUTPUT_LINES = 120;
|
|
31
|
+
const LINT_TARGETS = [
|
|
32
|
+
"README.md",
|
|
33
|
+
"package.json",
|
|
34
|
+
"tsconfig.json",
|
|
35
|
+
"vercel.json",
|
|
36
|
+
".oxlintrc.json",
|
|
37
|
+
".oxfmtrc.jsonc",
|
|
38
|
+
"scripts",
|
|
39
|
+
"site/code",
|
|
40
|
+
"site/content",
|
|
41
|
+
"site/assets",
|
|
42
|
+
"site/styles",
|
|
43
|
+
"site/site.jsonc",
|
|
44
|
+
];
|
|
31
45
|
const ROOT_TEST_FILE_PATTERNS = [
|
|
32
46
|
"*.test.ts",
|
|
33
47
|
"*.test.tsx",
|
|
@@ -86,7 +100,7 @@ const steps: CheckStep[] = [
|
|
|
86
100
|
name: "Internal",
|
|
87
101
|
},
|
|
88
102
|
{
|
|
89
|
-
command: [process.execPath, "x", "ultracite", "check"],
|
|
103
|
+
command: [process.execPath, "x", "ultracite", "check", ...LINT_TARGETS],
|
|
90
104
|
id: "lint",
|
|
91
105
|
name: "Lint",
|
|
92
106
|
},
|
|
@@ -142,6 +142,11 @@ const runSmokeChecks = async (): Promise<void> => {
|
|
|
142
142
|
await assertApiResponse();
|
|
143
143
|
};
|
|
144
144
|
|
|
145
|
+
const runProjectCheck = async (): Promise<void> => {
|
|
146
|
+
const check = await runCommand([process.execPath, "run", "check"]);
|
|
147
|
+
assertCommandOk("bun run check", check);
|
|
148
|
+
};
|
|
149
|
+
|
|
145
150
|
const startDev = (): {
|
|
146
151
|
devProc: ReturnType<typeof Bun.spawn>;
|
|
147
152
|
devStderr: Promise<string>;
|
|
@@ -174,19 +179,44 @@ const logDevFailure = async (args: {
|
|
|
174
179
|
console.error(stderr.trim() || "(empty)");
|
|
175
180
|
};
|
|
176
181
|
|
|
177
|
-
const
|
|
182
|
+
const toErrorMessage = (error: unknown): string => {
|
|
183
|
+
if (error instanceof Error) {
|
|
184
|
+
return error.message;
|
|
185
|
+
}
|
|
186
|
+
return String(error);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const runDevSmokeFlow = async (): Promise<number> => {
|
|
178
190
|
const { devProc, devStderr, devStdout } = startDev();
|
|
179
191
|
|
|
180
192
|
try {
|
|
181
193
|
await waitForReady();
|
|
182
194
|
await runSmokeChecks();
|
|
183
|
-
return 0;
|
|
184
195
|
} catch (error) {
|
|
185
196
|
await logDevFailure({ error, stderr: devStderr, stdout: devStdout });
|
|
186
197
|
return 1;
|
|
187
198
|
} finally {
|
|
188
199
|
await shutdownDev(devProc);
|
|
189
200
|
}
|
|
201
|
+
return 0;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const runPostDevCheck = async (): Promise<number> => {
|
|
205
|
+
try {
|
|
206
|
+
await runProjectCheck();
|
|
207
|
+
return 0;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error(toErrorMessage(error));
|
|
210
|
+
return 1;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const main = async (): Promise<number> => {
|
|
215
|
+
const smokeCode = await runDevSmokeFlow();
|
|
216
|
+
if (smokeCode !== 0) {
|
|
217
|
+
return smokeCode;
|
|
218
|
+
}
|
|
219
|
+
return runPostDevCheck();
|
|
190
220
|
};
|
|
191
221
|
|
|
192
222
|
const code = await main();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Optional: this file is here as the obvious place to put server-side code.
|
|
2
|
-
// V1 runs the built-in idcmd server; add custom endpoints via `site/
|
|
2
|
+
// V1 runs the built-in idcmd server; add custom endpoints via `site/code/routes/**`.
|
|
3
3
|
|
|
4
4
|
export const serverPlaceholder = true;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|