idcmd 0.0.10 → 0.0.12
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 +27 -5
- package/package.json +2 -3
- package/src/cli/commands/build.ts +2 -1
- package/src/cli/commands/deploy.ts +208 -22
- package/src/cli/commands/dev.ts +2 -1
- package/src/cli/commands/init.ts +56 -4
- package/src/cli/commands/preview.ts +36 -3
- package/src/cli/main.ts +271 -19
- package/src/cli/prompt.ts +105 -0
- package/src/cli/provider-files.ts +225 -0
- package/src/cli/provider.ts +36 -0
- package/src/render/layout.tsx +28 -31
- package/src/render/right-rail.tsx +5 -3
- package/src/search/index.ts +19 -24
- package/src/search/page.tsx +13 -11
- package/src/server/headers.ts +23 -4
- package/src/server/user-routes.ts +1 -1
- package/src/server.ts +22 -2
- package/src/site/cache.ts +108 -0
- package/src/site/config.ts +46 -4
- package/templates/default/.github/workflows/ci.yml +0 -3
- package/templates/default/README.md +6 -5
- package/templates/default/package.json +0 -1
- package/templates/default/scripts/check.ts +0 -1
- package/templates/default/site.jsonc +10 -0
- package/templates/default/src/server.ts +1 -1
- package/templates/default/src/ui/layout.tsx +27 -26
- package/templates/default/src/ui/right-rail.tsx +6 -3
- package/templates/default/src/ui/search-page.tsx +13 -10
- package/templates/default/tsconfig.json +1 -1
- package/templates/default/scripts/smoke.ts +0 -223
- package/templates/default/vercel.json +0 -7
package/README.md
CHANGED
|
@@ -18,11 +18,33 @@ idcmd init [dir] # scaffold a new site
|
|
|
18
18
|
idcmd dev # tailwind watch + SSR dev server
|
|
19
19
|
idcmd build # static public/
|
|
20
20
|
idcmd preview # serve public/ locally
|
|
21
|
-
idcmd deploy # build +
|
|
21
|
+
idcmd deploy # build + generate deploy files (Vercel/Fly/Railway)
|
|
22
22
|
idcmd client ... # add/update local src implementations
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
For full command docs (flags, examples, side effects), use:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
idcmd --help
|
|
29
|
+
idcmd init --help
|
|
30
|
+
idcmd deploy --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Deploy targets
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
idcmd init my-docs --fly
|
|
37
|
+
idcmd init my-docs --railway
|
|
38
|
+
idcmd init my-docs --vercel
|
|
39
|
+
|
|
40
|
+
idcmd deploy --fly
|
|
41
|
+
idcmd deploy --railway
|
|
42
|
+
idcmd deploy --vercel
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`idcmd init --yes` is provider-neutral by default (no provider files generated).
|
|
46
|
+
|
|
47
|
+
## Layout
|
|
26
48
|
|
|
27
49
|
- `content/<slug>.md` -> `/<slug>/` (`index.md` -> `/`)
|
|
28
50
|
- `src/ui/*` is local UI source code (you own and edit these files)
|
|
@@ -66,7 +88,7 @@ This is a new page.
|
|
|
66
88
|
|
|
67
89
|
It renders at `/hello/`.
|
|
68
90
|
|
|
69
|
-
## Custom Server Routes
|
|
91
|
+
## Custom Server Routes
|
|
70
92
|
|
|
71
93
|
Add `src/routes/api/hello.ts`:
|
|
72
94
|
|
|
@@ -76,9 +98,9 @@ export const GET = (): Response => Response.json({ ok: true });
|
|
|
76
98
|
|
|
77
99
|
It responds at `/api/hello`.
|
|
78
100
|
|
|
79
|
-
##
|
|
101
|
+
## Definition Of Done
|
|
80
102
|
|
|
81
|
-
|
|
103
|
+
We explicitly target:
|
|
82
104
|
|
|
83
105
|
- Content routes ship `0` bytes of JavaScript by default (both SSR output and built HTML).
|
|
84
106
|
- Content routes ship a small, opinionated JavaScript runtime by default (prefetch + optional right-rail behavior).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idcmd",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/rustydotwtf/idcmd"
|
|
@@ -35,8 +35,7 @@
|
|
|
35
35
|
"prepare": "lefthook install"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"
|
|
39
|
-
"preact-render-to-string": "^6.6.5",
|
|
38
|
+
"@kitajs/html": "^4.2.13",
|
|
40
39
|
"shiki": "^3.22.0",
|
|
41
40
|
"zod": "^3.24.0"
|
|
42
41
|
},
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
import { resolveCachePolicy } from "../../site/cache";
|
|
2
|
+
import { loadSiteConfig } from "../../site/config";
|
|
3
|
+
import { basename } from "../path";
|
|
4
|
+
import { isDeployProvider, resolveProviderFromFlags } from "../provider";
|
|
5
|
+
import { generateProviderFiles, providerConfigPaths } from "../provider-files";
|
|
1
6
|
import { buildCommand } from "./build";
|
|
2
7
|
|
|
8
|
+
export interface DeployFlags {
|
|
9
|
+
fly?: boolean;
|
|
10
|
+
railway?: boolean;
|
|
11
|
+
vercel?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
3
14
|
const readJsonFile = async (path: string): Promise<unknown> => {
|
|
4
15
|
const file = Bun.file(path);
|
|
5
16
|
if (!(await file.exists())) {
|
|
@@ -9,22 +20,81 @@ const readJsonFile = async (path: string): Promise<unknown> => {
|
|
|
9
20
|
return JSON.parse(text) as unknown;
|
|
10
21
|
};
|
|
11
22
|
|
|
23
|
+
const readPackageName = async (): Promise<string> => {
|
|
24
|
+
const raw = await readJsonFile("package.json");
|
|
25
|
+
if (raw && typeof raw === "object") {
|
|
26
|
+
const pkg = raw as { name?: unknown };
|
|
27
|
+
if (typeof pkg.name === "string" && pkg.name.length > 0) {
|
|
28
|
+
return pkg.name;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return basename(process.cwd());
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const warn = (message: string): void => {
|
|
35
|
+
// eslint-disable-next-line no-console
|
|
36
|
+
console.warn(`Warning: ${message}`);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const warnIfFileMissing = async (
|
|
40
|
+
path: string,
|
|
41
|
+
context: string
|
|
42
|
+
): Promise<void> => {
|
|
43
|
+
if (await Bun.file(path).exists()) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
warn(`${path} not found (${context}).`);
|
|
47
|
+
};
|
|
48
|
+
|
|
12
49
|
const warnIfVercelMisconfigured = async (): Promise<void> => {
|
|
13
|
-
const raw = await readJsonFile(
|
|
50
|
+
const raw = await readJsonFile(providerConfigPaths.vercelJson);
|
|
14
51
|
if (!raw) {
|
|
15
|
-
|
|
16
|
-
console.warn(
|
|
17
|
-
"Warning: vercel.json not found. Vercel static deploy expects public/ output."
|
|
18
|
-
);
|
|
52
|
+
warn("vercel.json not found. Run `idcmd deploy --vercel` to generate it.");
|
|
19
53
|
return;
|
|
20
54
|
}
|
|
21
55
|
|
|
22
56
|
const record = raw as Record<string, unknown>;
|
|
23
57
|
const out = record.outputDirectory;
|
|
24
58
|
if (out !== "public") {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
59
|
+
warn(
|
|
60
|
+
`vercel.json outputDirectory is not "public" (got ${JSON.stringify(out)}).`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const warnIfFlyMisconfigured = async (): Promise<void> => {
|
|
66
|
+
await warnIfFileMissing(
|
|
67
|
+
providerConfigPaths.flyToml,
|
|
68
|
+
"required for Fly.io deploy"
|
|
69
|
+
);
|
|
70
|
+
await warnIfFileMissing(
|
|
71
|
+
providerConfigPaths.dockerfile,
|
|
72
|
+
"required for Fly.io deploy"
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const warnIfRailwayMisconfigured = async (): Promise<void> => {
|
|
77
|
+
await warnIfFileMissing(
|
|
78
|
+
providerConfigPaths.railwayJson,
|
|
79
|
+
"required for Railway deploy"
|
|
80
|
+
);
|
|
81
|
+
await warnIfFileMissing(
|
|
82
|
+
providerConfigPaths.dockerfile,
|
|
83
|
+
"required for Railway deploy"
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const raw = await readJsonFile(providerConfigPaths.railwayJson);
|
|
87
|
+
if (!raw || typeof raw !== "object") {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const config = raw as {
|
|
92
|
+
build?: { builder?: unknown };
|
|
93
|
+
};
|
|
94
|
+
const builder = config.build?.builder;
|
|
95
|
+
if (builder !== undefined && builder !== "DOCKERFILE") {
|
|
96
|
+
warn(
|
|
97
|
+
`railway.json build.builder is ${JSON.stringify(builder)}; expected "DOCKERFILE".`
|
|
28
98
|
);
|
|
29
99
|
}
|
|
30
100
|
};
|
|
@@ -40,33 +110,78 @@ const warnIfBaseUrlMissing = async (): Promise<void> => {
|
|
|
40
110
|
baseUrl?: unknown;
|
|
41
111
|
};
|
|
42
112
|
if (!cfg.baseUrl) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"Warning: site.jsonc missing baseUrl; sitemap.xml and robots.txt will be skipped."
|
|
113
|
+
warn(
|
|
114
|
+
"site.jsonc missing baseUrl; sitemap.xml and robots.txt will be skipped."
|
|
46
115
|
);
|
|
47
116
|
}
|
|
48
117
|
} catch (error) {
|
|
49
118
|
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
-
|
|
51
|
-
console.warn(`Warning: Failed to parse site.jsonc: ${message}`);
|
|
119
|
+
warn(`Failed to parse site.jsonc: ${message}`);
|
|
52
120
|
}
|
|
53
121
|
};
|
|
54
122
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
123
|
+
const warnIfProviderMisconfigured = async (
|
|
124
|
+
provider: "vercel" | "fly" | "railway"
|
|
125
|
+
): Promise<void> => {
|
|
126
|
+
if (provider === "vercel") {
|
|
127
|
+
await warnIfVercelMisconfigured();
|
|
128
|
+
return;
|
|
59
129
|
}
|
|
130
|
+
if (provider === "fly") {
|
|
131
|
+
await warnIfFlyMisconfigured();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
await warnIfRailwayMisconfigured();
|
|
135
|
+
};
|
|
60
136
|
|
|
61
|
-
|
|
62
|
-
|
|
137
|
+
const printGeneratedFiles = (files: readonly string[]): void => {
|
|
138
|
+
if (files.length === 0) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// eslint-disable-next-line no-console
|
|
142
|
+
console.log("");
|
|
143
|
+
// eslint-disable-next-line no-console
|
|
144
|
+
console.log("Generated files:");
|
|
145
|
+
for (const path of files) {
|
|
146
|
+
// eslint-disable-next-line no-console
|
|
147
|
+
console.log(` - ${path}`);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
63
150
|
|
|
64
|
-
|
|
151
|
+
const generateProviderFilesForProject = async (
|
|
152
|
+
provider: "vercel" | "fly" | "railway"
|
|
153
|
+
): Promise<void> => {
|
|
154
|
+
const siteConfig = await loadSiteConfig();
|
|
155
|
+
const packageName = await readPackageName();
|
|
156
|
+
const files = await generateProviderFiles({
|
|
157
|
+
cachePolicy: resolveCachePolicy(siteConfig.cache),
|
|
158
|
+
packageName,
|
|
159
|
+
provider,
|
|
160
|
+
targetDir: process.cwd(),
|
|
161
|
+
});
|
|
162
|
+
printGeneratedFiles(files);
|
|
163
|
+
};
|
|
65
164
|
|
|
66
|
-
|
|
165
|
+
const printNeutralDeployInstructions = (): void => {
|
|
166
|
+
// eslint-disable-next-line no-console
|
|
167
|
+
console.log("");
|
|
168
|
+
// eslint-disable-next-line no-console
|
|
169
|
+
console.log("Deploy:");
|
|
170
|
+
// eslint-disable-next-line no-console
|
|
171
|
+
console.log(" 1. Choose a provider");
|
|
172
|
+
// eslint-disable-next-line no-console
|
|
173
|
+
console.log(" 2. Generate provider files:");
|
|
174
|
+
// eslint-disable-next-line no-console
|
|
175
|
+
console.log(" idcmd deploy --vercel");
|
|
176
|
+
// eslint-disable-next-line no-console
|
|
177
|
+
console.log(" idcmd deploy --fly");
|
|
178
|
+
// eslint-disable-next-line no-console
|
|
179
|
+
console.log(" idcmd deploy --railway");
|
|
180
|
+
// eslint-disable-next-line no-console
|
|
181
|
+
console.log("");
|
|
67
182
|
};
|
|
68
183
|
|
|
69
|
-
const
|
|
184
|
+
const printVercelInstructions = (): void => {
|
|
70
185
|
// eslint-disable-next-line no-console
|
|
71
186
|
console.log("");
|
|
72
187
|
// eslint-disable-next-line no-console
|
|
@@ -80,3 +195,74 @@ const printDeployInstructions = (): void => {
|
|
|
80
195
|
// eslint-disable-next-line no-console
|
|
81
196
|
console.log("");
|
|
82
197
|
};
|
|
198
|
+
|
|
199
|
+
const printFlyInstructions = (): void => {
|
|
200
|
+
// eslint-disable-next-line no-console
|
|
201
|
+
console.log("");
|
|
202
|
+
// eslint-disable-next-line no-console
|
|
203
|
+
console.log("Deploy (Fly.io):");
|
|
204
|
+
// eslint-disable-next-line no-console
|
|
205
|
+
console.log(" 1. Install flyctl and run `fly auth login`");
|
|
206
|
+
// eslint-disable-next-line no-console
|
|
207
|
+
console.log(" 2. Set `app` in fly.toml to your Fly app name");
|
|
208
|
+
// eslint-disable-next-line no-console
|
|
209
|
+
console.log(" 3. Run `fly deploy`");
|
|
210
|
+
// eslint-disable-next-line no-console
|
|
211
|
+
console.log("");
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const printRailwayInstructions = (): void => {
|
|
215
|
+
// eslint-disable-next-line no-console
|
|
216
|
+
console.log("");
|
|
217
|
+
// eslint-disable-next-line no-console
|
|
218
|
+
console.log("Deploy (Railway):");
|
|
219
|
+
// eslint-disable-next-line no-console
|
|
220
|
+
console.log(" 1. Connect this repo in Railway (or use `railway up`)");
|
|
221
|
+
// eslint-disable-next-line no-console
|
|
222
|
+
console.log(" 2. Railway will build from Dockerfile");
|
|
223
|
+
// eslint-disable-next-line no-console
|
|
224
|
+
console.log(
|
|
225
|
+
" 3. Ensure required env vars are set (for example SITE_BASE_URL)"
|
|
226
|
+
);
|
|
227
|
+
// eslint-disable-next-line no-console
|
|
228
|
+
console.log("");
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const printDeployInstructions = (
|
|
232
|
+
provider: "none" | "vercel" | "fly" | "railway"
|
|
233
|
+
): void => {
|
|
234
|
+
if (provider === "none") {
|
|
235
|
+
printNeutralDeployInstructions();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (provider === "vercel") {
|
|
239
|
+
printVercelInstructions();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (provider === "fly") {
|
|
243
|
+
printFlyInstructions();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
printRailwayInstructions();
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export const deployCommand = async (
|
|
250
|
+
flags: DeployFlags = {}
|
|
251
|
+
): Promise<number> => {
|
|
252
|
+
const provider = resolveProviderFromFlags(flags);
|
|
253
|
+
const code = await buildCommand();
|
|
254
|
+
if (code !== 0) {
|
|
255
|
+
return code;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
await warnIfBaseUrlMissing();
|
|
259
|
+
|
|
260
|
+
if (isDeployProvider(provider)) {
|
|
261
|
+
await generateProviderFilesForProject(provider);
|
|
262
|
+
await warnIfProviderMisconfigured(provider);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
printDeployInstructions(provider);
|
|
266
|
+
|
|
267
|
+
return 0;
|
|
268
|
+
};
|
package/src/cli/commands/dev.ts
CHANGED
package/src/cli/commands/init.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveCachePolicy } from "../../site/cache";
|
|
1
2
|
import { copyDir, ensureDir, isDirEmpty, replaceInFile } from "../fs";
|
|
2
3
|
import {
|
|
3
4
|
normalizeOptionalString,
|
|
@@ -5,7 +6,9 @@ import {
|
|
|
5
6
|
toPackageName,
|
|
6
7
|
} from "../normalize";
|
|
7
8
|
import { basename, dirname, joinPath } from "../path";
|
|
8
|
-
import { promptOptionalText, promptText } from "../prompt";
|
|
9
|
+
import { promptOptionalText, promptSelect, promptText } from "../prompt";
|
|
10
|
+
import { isDeployProvider, resolveProviderFromFlags } from "../provider";
|
|
11
|
+
import { generateProviderFiles } from "../provider-files";
|
|
9
12
|
import { run } from "../run";
|
|
10
13
|
import { readPackageVersion } from "../version";
|
|
11
14
|
|
|
@@ -14,9 +17,12 @@ const DEFAULT_PORT = 4000;
|
|
|
14
17
|
export interface InitFlags {
|
|
15
18
|
"base-url"?: string;
|
|
16
19
|
description?: string;
|
|
20
|
+
fly?: boolean;
|
|
17
21
|
git?: boolean;
|
|
18
22
|
name?: string;
|
|
19
23
|
port?: string;
|
|
24
|
+
railway?: boolean;
|
|
25
|
+
vercel?: boolean;
|
|
20
26
|
yes?: boolean;
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -110,7 +116,7 @@ const fillPackageJson = (args: {
|
|
|
110
116
|
.replaceAll("__IDCMD_IDCMD_VERSION__", `^${args.idcmdVersion}`)
|
|
111
117
|
.replaceAll("__IDCMD_DEV_PORT__", String(args.port));
|
|
112
118
|
|
|
113
|
-
const
|
|
119
|
+
const fillSiteNameTokens = (args: { siteName: string; text: string }): string =>
|
|
114
120
|
args.text
|
|
115
121
|
.replaceAll("__IDCMD_SITE_NAME__", args.siteName)
|
|
116
122
|
.replaceAll("IDCMD_SITE_NAME", args.siteName);
|
|
@@ -125,14 +131,41 @@ interface InitInputs {
|
|
|
125
131
|
baseUrl: string | null;
|
|
126
132
|
description: string;
|
|
127
133
|
port: number;
|
|
134
|
+
provider: "none" | "vercel" | "fly" | "railway";
|
|
128
135
|
siteName: string;
|
|
129
136
|
}
|
|
130
137
|
|
|
138
|
+
const promptProviderChoice = (): Promise<
|
|
139
|
+
"none" | "vercel" | "fly" | "railway"
|
|
140
|
+
> =>
|
|
141
|
+
promptSelect(
|
|
142
|
+
"Deploy provider (optional)",
|
|
143
|
+
[
|
|
144
|
+
{ label: "None (self-host / decide later)", value: "none" },
|
|
145
|
+
{ label: "Vercel", value: "vercel" },
|
|
146
|
+
{ label: "Fly.io", value: "fly" },
|
|
147
|
+
{ label: "Railway", value: "railway" },
|
|
148
|
+
],
|
|
149
|
+
"none"
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const resolveInitProvider = (
|
|
153
|
+
flags: InitFlags,
|
|
154
|
+
yes: boolean
|
|
155
|
+
): Promise<"none" | "vercel" | "fly" | "railway"> => {
|
|
156
|
+
const provider = resolveProviderFromFlags(flags);
|
|
157
|
+
if (provider !== "none" || yes) {
|
|
158
|
+
return Promise.resolve(provider);
|
|
159
|
+
}
|
|
160
|
+
return promptProviderChoice();
|
|
161
|
+
};
|
|
162
|
+
|
|
131
163
|
const readInitInputs = async (
|
|
132
164
|
flags: InitFlags,
|
|
133
165
|
defaults: InitDefaults
|
|
134
166
|
): Promise<InitInputs> => {
|
|
135
167
|
const yes = flags.yes === true;
|
|
168
|
+
const provider = await resolveInitProvider(flags, yes);
|
|
136
169
|
|
|
137
170
|
const siteName = yes
|
|
138
171
|
? (flags.name ?? defaults.defaultSiteName)
|
|
@@ -157,6 +190,7 @@ const readInitInputs = async (
|
|
|
157
190
|
baseUrl: normalizeOptionalString(baseUrlRaw),
|
|
158
191
|
description,
|
|
159
192
|
port,
|
|
193
|
+
provider,
|
|
160
194
|
siteName,
|
|
161
195
|
};
|
|
162
196
|
};
|
|
@@ -219,7 +253,14 @@ const applySubstitutions = async (args: {
|
|
|
219
253
|
);
|
|
220
254
|
|
|
221
255
|
await replaceInFile(joinPath(args.targetDir, "README.md"), (text) =>
|
|
222
|
-
|
|
256
|
+
fillSiteNameTokens({
|
|
257
|
+
siteName: args.siteName,
|
|
258
|
+
text,
|
|
259
|
+
})
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
await replaceInFile(joinPath(args.targetDir, "content", "index.md"), (text) =>
|
|
263
|
+
fillSiteNameTokens({
|
|
223
264
|
siteName: args.siteName,
|
|
224
265
|
text,
|
|
225
266
|
})
|
|
@@ -248,7 +289,9 @@ const printNextSteps = (dir: string): void => {
|
|
|
248
289
|
const assertEmptyTargetDir = async (targetDir: string): Promise<void> => {
|
|
249
290
|
const empty = await isDirEmpty(targetDir);
|
|
250
291
|
if (!empty) {
|
|
251
|
-
throw new Error(
|
|
292
|
+
throw new Error(
|
|
293
|
+
`Target directory is not empty: ${targetDir}\nUse an empty directory or run \`idcmd init <new-directory>\`.`
|
|
294
|
+
);
|
|
252
295
|
}
|
|
253
296
|
};
|
|
254
297
|
|
|
@@ -280,6 +323,15 @@ const scaffoldAndConfigure = async (args: {
|
|
|
280
323
|
siteName: args.inputs.siteName,
|
|
281
324
|
targetDir: args.targetDir,
|
|
282
325
|
});
|
|
326
|
+
|
|
327
|
+
if (isDeployProvider(args.inputs.provider)) {
|
|
328
|
+
await generateProviderFiles({
|
|
329
|
+
cachePolicy: resolveCachePolicy(),
|
|
330
|
+
packageName: args.defaults.packageName,
|
|
331
|
+
provider: args.inputs.provider,
|
|
332
|
+
targetDir: args.targetDir,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
283
335
|
};
|
|
284
336
|
|
|
285
337
|
export const initCommand = async (
|
|
@@ -6,12 +6,15 @@ import {
|
|
|
6
6
|
const stripLeadingSlash = (pathname: string): string =>
|
|
7
7
|
pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
8
8
|
|
|
9
|
-
const tryServeFile = async (
|
|
9
|
+
const tryServeFile = async (
|
|
10
|
+
relativePath: string,
|
|
11
|
+
status = 200
|
|
12
|
+
): Promise<Response | null> => {
|
|
10
13
|
const file = Bun.file(`public/${stripLeadingSlash(relativePath)}`);
|
|
11
14
|
if (!(await file.exists())) {
|
|
12
15
|
return null;
|
|
13
16
|
}
|
|
14
|
-
return new Response(file);
|
|
17
|
+
return new Response(file, { status });
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
const toHtmlEntryPath = (canonicalPathname: string): string =>
|
|
@@ -32,10 +35,36 @@ const serveHtml = async (pathname: string): Promise<Response> => {
|
|
|
32
35
|
return served;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
const notFound = await tryServeFile("404.html");
|
|
38
|
+
const notFound = await tryServeFile("404.html", 404);
|
|
36
39
|
return notFound ?? new Response("Not Found", { status: 404 });
|
|
37
40
|
};
|
|
38
41
|
|
|
42
|
+
type ShutdownSignal = "SIGINT" | "SIGTERM";
|
|
43
|
+
|
|
44
|
+
const waitForShutdownSignal = (): Promise<ShutdownSignal> => {
|
|
45
|
+
const { promise, resolve } = Promise.withResolvers<ShutdownSignal>();
|
|
46
|
+
|
|
47
|
+
const handleSigInt = (): void => {
|
|
48
|
+
cleanup();
|
|
49
|
+
resolve("SIGINT");
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleSigTerm = (): void => {
|
|
53
|
+
cleanup();
|
|
54
|
+
resolve("SIGTERM");
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const cleanup = (): void => {
|
|
58
|
+
process.off("SIGINT", handleSigInt);
|
|
59
|
+
process.off("SIGTERM", handleSigTerm);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
process.on("SIGINT", handleSigInt);
|
|
63
|
+
process.on("SIGTERM", handleSigTerm);
|
|
64
|
+
|
|
65
|
+
return promise;
|
|
66
|
+
};
|
|
67
|
+
|
|
39
68
|
export const previewCommand = async (port: number): Promise<number> => {
|
|
40
69
|
const exists = await Bun.file("public/index.html").exists();
|
|
41
70
|
if (!exists) {
|
|
@@ -56,5 +85,9 @@ export const previewCommand = async (port: number): Promise<number> => {
|
|
|
56
85
|
|
|
57
86
|
// eslint-disable-next-line no-console
|
|
58
87
|
console.log(`Preview running at http://localhost:${server.port}`);
|
|
88
|
+
|
|
89
|
+
await waitForShutdownSignal();
|
|
90
|
+
server.stop(true);
|
|
91
|
+
|
|
59
92
|
return 0;
|
|
60
93
|
};
|