site-index 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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +107 -0
  3. package/dist/bin.d.ts +2 -0
  4. package/dist/bin.js +3 -0
  5. package/dist/domains/make/adapters/handlebars.d.ts +3 -0
  6. package/dist/domains/make/adapters/handlebars.js +6 -0
  7. package/dist/domains/make/adapters/site-index.d.ts +8 -0
  8. package/dist/domains/make/adapters/site-index.js +18 -0
  9. package/dist/domains/make/commands/make.command.d.ts +2 -0
  10. package/dist/domains/make/commands/make.command.js +15 -0
  11. package/dist/domains/make/make.service.d.ts +2 -0
  12. package/dist/domains/make/make.service.js +14 -0
  13. package/dist/domains/make/schemas/make.schema.d.ts +14 -0
  14. package/dist/domains/make/schemas/make.schema.js +43 -0
  15. package/dist/domains/make/templates/site-index.esm.hbs +9 -0
  16. package/dist/domains/make/templates/site-index.ts.hbs +10 -0
  17. package/dist/domains/make/types.d.ts +8 -0
  18. package/dist/domains/make/types.js +1 -0
  19. package/dist/domains/site-indexes/build.service.d.ts +2 -0
  20. package/dist/domains/site-indexes/build.service.js +35 -0
  21. package/dist/domains/site-indexes/check.service.d.ts +2 -0
  22. package/dist/domains/site-indexes/check.service.js +22 -0
  23. package/dist/domains/site-indexes/commands/build.command.d.ts +2 -0
  24. package/dist/domains/site-indexes/commands/build.command.js +13 -0
  25. package/dist/domains/site-indexes/commands/check.command.d.ts +2 -0
  26. package/dist/domains/site-indexes/commands/check.command.js +12 -0
  27. package/dist/domains/site-indexes/schemas/build.schema.d.ts +13 -0
  28. package/dist/domains/site-indexes/schemas/build.schema.js +23 -0
  29. package/dist/domains/site-indexes/schemas/check.schema.d.ts +9 -0
  30. package/dist/domains/site-indexes/schemas/check.schema.js +2 -0
  31. package/dist/domains/site-indexes/schemas/shared.schema.d.ts +11 -0
  32. package/dist/domains/site-indexes/schemas/shared.schema.js +45 -0
  33. package/dist/domains/site-indexes/types.d.ts +9 -0
  34. package/dist/domains/site-indexes/types.js +1 -0
  35. package/dist/domains/site-indexes/vite.config.d.ts +3 -0
  36. package/dist/domains/site-indexes/vite.config.js +7 -0
  37. package/dist/main.d.ts +1 -0
  38. package/dist/main.js +38 -0
  39. package/dist/shared/logging/logger.d.ts +2 -0
  40. package/dist/shared/logging/logger.js +2 -0
  41. package/dist/shared/utils/fs.d.ts +1 -0
  42. package/dist/shared/utils/fs.js +16 -0
  43. package/dist/shared/utils/path.d.ts +1 -0
  44. package/dist/shared/utils/path.js +6 -0
  45. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Deasilsoft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # site-index
2
+
3
+ CLI for creating site-index modules and generating/checking sitemap and robots.txt artifacts.
4
+
5
+ [Repository README](../../README.md)
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -D site-index
11
+ ```
12
+
13
+ Requirements:
14
+
15
+ - Node.js `>=22`
16
+
17
+ ## When to use
18
+
19
+ Use `site-index` when you want command-line workflows for:
20
+
21
+ - scaffolding `*.site-index.*` modules
22
+ - generating sitemap and robots.txt artifacts
23
+ - validating site-index modules in CI
24
+
25
+ ## When not to use
26
+
27
+ - Use [`@site-index/vite-plugin`](../@site-index/vite-plugin/README.md) for Vite-native integration.
28
+ - Use [`@site-index/core`](../@site-index/core/README.md) for custom programmatic pipelines.
29
+
30
+ ## Global options
31
+
32
+ - `--quiet`: suppress informational output
33
+ - `--verbose`: enable verbose logging
34
+
35
+ ## Commands
36
+
37
+ ### `make`
38
+
39
+ ```bash
40
+ site-index make <filePath> [--format <format>] [--force]
41
+ ```
42
+
43
+ Behavior:
44
+
45
+ - creates a new site-index module template
46
+ - supports `--format ts`
47
+ - supports `--format esm`
48
+ - defaults format to `ts`
49
+ - refuses to overwrite existing files unless `--force` is used
50
+ - normalizes output filenames to:
51
+ - `<name>.site-index.ts`
52
+ - `<name>.site-index.mjs`
53
+
54
+ ### `build`
55
+
56
+ ```bash
57
+ site-index build --site-url <url> [--root <path>] [--out <dir>] [--config <path>]
58
+ ```
59
+
60
+ Behavior:
61
+
62
+ - generates site-index artifacts
63
+ - writes artifacts to `--out` (default: `dist`)
64
+ - requires `--site-url`
65
+ - validates `--site-url` as a valid URL
66
+ - validates `--out` resolves within `--root`
67
+ - uses Vite config when `--config` is provided
68
+ - `--root` defaults to current working directory
69
+ - `--config` must resolve within `--root`
70
+
71
+ ### `check`
72
+
73
+ ```bash
74
+ site-index check --site-url <url> [--root <path>] [--config <path>]
75
+ ```
76
+
77
+ Behavior:
78
+
79
+ - validates discovered site-index modules for CI
80
+ - requires `--site-url`
81
+ - fails when warnings are produced
82
+ - uses Vite config when `--config` is provided
83
+ - `--root` defaults to current working directory
84
+ - `--config` must resolve within `--root`
85
+
86
+ ## Examples
87
+
88
+ ```bash
89
+ npx site-index make src/pages.site-index.ts --format ts
90
+ npx site-index make src/pages.site-index.mjs --format esm
91
+ npx site-index build --site-url https://example.com
92
+ npx site-index check --site-url https://example.com
93
+ ```
94
+
95
+ Local monorepo development:
96
+
97
+ ```bash
98
+ npm run cli -- build --site-url https://example.com
99
+ ```
100
+
101
+ ## How it fits in the monorepo
102
+
103
+ `site-index` is the user-facing CLI package built on:
104
+
105
+ - [`@site-index/vite-runtime`](../@site-index/vite-runtime/README.md)
106
+ - [`@site-index/core`](../@site-index/core/README.md)
107
+ - [`@site-index/observability`](../@site-index/observability/README.md)
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "./main.js";
3
+ await main();
@@ -0,0 +1,3 @@
1
+ type TemplateContext = Record<string, string>;
2
+ export declare function renderTemplate(template: string, context: TemplateContext): string;
3
+ export {};
@@ -0,0 +1,6 @@
1
+ const TOKEN_PATTERN = /\{\{\s*([a-zA-Z0-9_]+)\s*}}/g;
2
+ export function renderTemplate(template, context) {
3
+ return template.replaceAll(TOKEN_PATTERN, (token, variableName) => {
4
+ return context[variableName] ?? token;
5
+ });
6
+ }
@@ -0,0 +1,8 @@
1
+ import type { Format } from "../types.js";
2
+ type TemplateData = {
3
+ filePath: string;
4
+ format: Format;
5
+ lastModified: string;
6
+ };
7
+ export declare function makeSiteIndexModule(data: TemplateData): Promise<void>;
8
+ export {};
@@ -0,0 +1,18 @@
1
+ import NodeFS from "node:fs/promises";
2
+ import NodePath from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { renderTemplate } from "./handlebars.js";
5
+ const DIR = NodePath.dirname(fileURLToPath(import.meta.url));
6
+ const TEMPLATES = {
7
+ esm: NodePath.resolve(DIR, "../templates/site-index.esm.hbs"),
8
+ ts: NodePath.resolve(DIR, "../templates/site-index.ts.hbs"),
9
+ };
10
+ export async function makeSiteIndexModule(data) {
11
+ const templatePath = TEMPLATES[data.format];
12
+ const template = await NodeFS.readFile(templatePath, "utf8");
13
+ const content = renderTemplate(template, {
14
+ lastModified: data.lastModified,
15
+ });
16
+ await NodeFS.mkdir(NodePath.dirname(data.filePath), { recursive: true });
17
+ await NodeFS.writeFile(data.filePath, content, "utf8");
18
+ }
@@ -0,0 +1,2 @@
1
+ import type { CAC } from "cac";
2
+ export declare function initMakeCommand(cli: CAC): void;
@@ -0,0 +1,15 @@
1
+ import { runMake } from "../make.service.js";
2
+ import { MakeConfigSchema } from "../schemas/make.schema.js";
3
+ export function initMakeCommand(cli) {
4
+ cli
5
+ .command("make <filePath>", "Create a new site-index module")
6
+ .option("--format <format>", "Module format (ts | esm)")
7
+ .option("--force", "Overwrite if the file already exists")
8
+ .action(async (filePath, options) => {
9
+ await runMake(MakeConfigSchema.parse({
10
+ filePath,
11
+ format: options.format,
12
+ force: options.force,
13
+ }));
14
+ });
15
+ }
@@ -0,0 +1,2 @@
1
+ import type { MakeConfig } from "./types.js";
2
+ export declare function runMake(config: MakeConfig): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { logger } from "../../shared/logging/logger.js";
2
+ import { fileExists } from "../../shared/utils/fs.js";
3
+ import { makeSiteIndexModule } from "./adapters/site-index.js";
4
+ export async function runMake(config) {
5
+ if ((await fileExists(config.outputFilePath)) && !config.force) {
6
+ throw new Error(`Refusing to overwrite existing file: ${config.outputFilePath} (use --force)`);
7
+ }
8
+ await makeSiteIndexModule({
9
+ filePath: config.outputFilePath,
10
+ format: config.format,
11
+ lastModified: new Date().toISOString(),
12
+ });
13
+ logger.info(`Created file: ${config.outputFilePath}`);
14
+ }
@@ -0,0 +1,14 @@
1
+ import { z as Zod } from "zod";
2
+ import { type MakeConfig } from "../types.js";
3
+ export declare const MakeConfigSchema: Zod.ZodPipe<Zod.ZodObject<{
4
+ filePath: Zod.ZodString;
5
+ format: Zod.ZodDefault<Zod.ZodEnum<{
6
+ ts: "ts";
7
+ esm: "esm";
8
+ }>>;
9
+ force: Zod.ZodDefault<Zod.ZodBoolean>;
10
+ }, Zod.core.$strip>, Zod.ZodTransform<MakeConfig, {
11
+ filePath: string;
12
+ format: "ts" | "esm";
13
+ force: boolean;
14
+ }>>;
@@ -0,0 +1,43 @@
1
+ import NodePath from "node:path";
2
+ import { z as Zod } from "zod";
3
+ import { isRelativePathEscapingRoot } from "../../../shared/utils/path.js";
4
+ import { FORMATS } from "../types.js";
5
+ const EXTENSIONS = {
6
+ ts: "ts",
7
+ esm: "mjs",
8
+ };
9
+ const EXT_PATTERN = Object.values(EXTENSIONS).join("|");
10
+ function isWithinCurrentWorkingDirectory(filePath) {
11
+ const cwd = process.cwd();
12
+ const resolved = NodePath.resolve(filePath);
13
+ const relative = NodePath.relative(cwd, resolved);
14
+ return relative !== "" && !isRelativePathEscapingRoot(relative);
15
+ }
16
+ function normalizeBaseName(filePath) {
17
+ const name = NodePath.basename(filePath);
18
+ return name
19
+ .replace(new RegExp(String.raw `\.site-index\.(${EXT_PATTERN})$`), "")
20
+ .replace(new RegExp(String.raw `\.(${EXT_PATTERN})$`), "");
21
+ }
22
+ const MakeOptionsSchema = Zod.object({
23
+ filePath: Zod.string()
24
+ .trim()
25
+ .min(1, { error: "File path is required" })
26
+ .refine(isWithinCurrentWorkingDirectory, {
27
+ error: "File path must stay within the current working directory",
28
+ }),
29
+ format: Zod.enum(FORMATS, {
30
+ error: "Invalid option: --format must be one of: ts, esm",
31
+ }).default("ts"),
32
+ force: Zod.boolean().default(false),
33
+ });
34
+ export const MakeConfigSchema = MakeOptionsSchema.transform((options) => {
35
+ const resolved = NodePath.resolve(options.filePath);
36
+ const dir = NodePath.dirname(resolved);
37
+ const base = normalizeBaseName(resolved);
38
+ const ext = EXTENSIONS[options.format];
39
+ return {
40
+ ...options,
41
+ outputFilePath: NodePath.resolve(dir, `${base}.site-index.${ext}`),
42
+ };
43
+ });
@@ -0,0 +1,9 @@
1
+ /** @type {import("@site-index/core").SiteIndex[]} */
2
+ const siteIndexes = [
3
+ {
4
+ url: "/",
5
+ lastModified: "{{lastModified}}",
6
+ },
7
+ ];
8
+
9
+ export default { siteIndexes };
@@ -0,0 +1,10 @@
1
+ import type { SiteIndex } from "@site-index/core";
2
+
3
+ const siteIndexes = [
4
+ {
5
+ url: "/",
6
+ lastModified: "{{lastModified}}",
7
+ },
8
+ ] satisfies SiteIndex[];
9
+
10
+ export default { siteIndexes };
@@ -0,0 +1,8 @@
1
+ export declare const FORMATS: readonly ["ts", "esm"];
2
+ export type Format = (typeof FORMATS)[number];
3
+ export type MakeConfig = {
4
+ filePath: string;
5
+ format: Format;
6
+ force: boolean;
7
+ outputFilePath: string;
8
+ };
@@ -0,0 +1 @@
1
+ export const FORMATS = ["ts", "esm"];
@@ -0,0 +1,2 @@
1
+ import type { BuildConfig } from "./types.js";
2
+ export declare function runBuild(config: BuildConfig): Promise<void>;
@@ -0,0 +1,35 @@
1
+ import { createRuntimeService } from "@site-index/vite-runtime";
2
+ import NodeFS from "node:fs/promises";
3
+ import NodePath from "node:path";
4
+ import { logger } from "../../shared/logging/logger.js";
5
+ import { isRelativePathEscapingRoot } from "../../shared/utils/path.js";
6
+ import { makeResolvedViteConfig } from "./vite.config.js";
7
+ async function writeArtifacts(outPath, artifacts) {
8
+ const resolvedOutPath = NodePath.resolve(outPath);
9
+ for (const artifact of artifacts) {
10
+ const filePath = NodePath.resolve(resolvedOutPath, artifact.filePath);
11
+ const relativePath = NodePath.relative(resolvedOutPath, filePath);
12
+ if (isRelativePathEscapingRoot(relativePath)) {
13
+ throw new Error(`Artifact path escapes output directory: ${artifact.filePath}`);
14
+ }
15
+ await NodeFS.mkdir(NodePath.dirname(filePath), { recursive: true });
16
+ await NodeFS.writeFile(filePath, artifact.content, "utf8");
17
+ }
18
+ }
19
+ export async function runBuild(config) {
20
+ const runtime = createRuntimeService()
21
+ .withOptions({
22
+ siteUrl: config.siteUrl,
23
+ extensions: undefined,
24
+ })
25
+ .withViteConfig(makeResolvedViteConfig(config))
26
+ .build();
27
+ try {
28
+ const result = await runtime.buildArtifacts();
29
+ logger.warn(result.warnings);
30
+ await writeArtifacts(config.outPath, result.data);
31
+ }
32
+ finally {
33
+ await runtime.close();
34
+ }
35
+ }
@@ -0,0 +1,2 @@
1
+ import type { CheckConfig } from "./types.js";
2
+ export declare function runCheck(config: CheckConfig): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { createRuntimeService } from "@site-index/vite-runtime";
2
+ import { logger } from "../../shared/logging/logger.js";
3
+ import { makeResolvedViteConfig } from "./vite.config.js";
4
+ export async function runCheck(config) {
5
+ const runtime = createRuntimeService()
6
+ .withOptions({
7
+ siteUrl: config.siteUrl,
8
+ extensions: undefined,
9
+ })
10
+ .withViteConfig(makeResolvedViteConfig(config))
11
+ .build();
12
+ try {
13
+ const result = await runtime.buildArtifacts();
14
+ if (result.warnings.length > 0) {
15
+ logger.warn(result.warnings);
16
+ throw new Error(`Check failed with ${result.warnings.length} warning(s)`);
17
+ }
18
+ }
19
+ finally {
20
+ await runtime.close();
21
+ }
22
+ }
@@ -0,0 +1,2 @@
1
+ import type { CAC } from "cac";
2
+ export declare function initBuildCommand(cli: CAC): void;
@@ -0,0 +1,13 @@
1
+ import { runBuild } from "../build.service.js";
2
+ import { BuildConfigSchema } from "../schemas/build.schema.js";
3
+ export function initBuildCommand(cli) {
4
+ cli
5
+ .command("build", "Generate site-index artifacts")
6
+ .option("--site-url <url>", "Site URL used for absolute sitemap links")
7
+ .option("--root <path>", "Project root")
8
+ .option("--out <dir>", "Output directory (relative to root)")
9
+ .option("--config <path>", "Path to Vite config")
10
+ .action(async (options) => {
11
+ await runBuild(BuildConfigSchema.parse(options));
12
+ });
13
+ }
@@ -0,0 +1,2 @@
1
+ import type { CAC } from "cac";
2
+ export declare function initCheckCommand(cli: CAC): void;
@@ -0,0 +1,12 @@
1
+ import { runCheck } from "../check.service.js";
2
+ import { CheckConfigSchema } from "../schemas/check.schema.js";
3
+ export function initCheckCommand(cli) {
4
+ cli
5
+ .command("check", "Validate site-index modules for CI")
6
+ .option("--site-url <url>", "Site URL used for absolute sitemap links")
7
+ .option("--config <path>", "Path to Vite config")
8
+ .option("--root <path>", "Project root")
9
+ .action(async (options) => {
10
+ await runCheck(CheckConfigSchema.parse(options));
11
+ });
12
+ }
@@ -0,0 +1,13 @@
1
+ import { z as Zod } from "zod";
2
+ import { type BuildConfig } from "../types.js";
3
+ export declare const BuildConfigSchema: Zod.ZodPipe<Zod.ZodObject<{
4
+ siteUrl: Zod.ZodPipe<Zod.ZodString, Zod.ZodURL>;
5
+ root: Zod.ZodOptional<Zod.ZodString>;
6
+ config: Zod.ZodOptional<Zod.ZodString>;
7
+ out: Zod.ZodDefault<Zod.ZodString>;
8
+ }, Zod.core.$strip>, Zod.ZodTransform<BuildConfig, {
9
+ siteUrl: string;
10
+ out: string;
11
+ root?: string | undefined;
12
+ config?: string | undefined;
13
+ }>>;
@@ -0,0 +1,23 @@
1
+ import NodePath from "node:path";
2
+ import { z as Zod } from "zod";
3
+ import {} from "../types.js";
4
+ import { BaseOptionsSchema, isPathWithinRoot, resolveBaseConfig, } from "./shared.schema.js";
5
+ const BuildOptionsSchema = BaseOptionsSchema.extend({
6
+ out: Zod.string().trim().min(1).default("dist"),
7
+ }).superRefine((options, context) => {
8
+ const rootPath = NodePath.resolve(options.root ?? process.cwd());
9
+ if (!isPathWithinRoot(rootPath, options.out)) {
10
+ context.addIssue({
11
+ code: "custom",
12
+ path: ["out"],
13
+ message: "Invalid option: --out must resolve within --root",
14
+ });
15
+ }
16
+ });
17
+ export const BuildConfigSchema = BuildOptionsSchema.transform(({ out, ...options }) => {
18
+ const baseConfig = resolveBaseConfig(options);
19
+ return {
20
+ ...baseConfig,
21
+ outPath: NodePath.resolve(baseConfig.rootPath, out),
22
+ };
23
+ });
@@ -0,0 +1,9 @@
1
+ export declare const CheckConfigSchema: import("zod").ZodPipe<import("zod").ZodObject<{
2
+ siteUrl: import("zod").ZodPipe<import("zod").ZodString, import("zod").ZodURL>;
3
+ root: import("zod").ZodOptional<import("zod").ZodString>;
4
+ config: import("zod").ZodOptional<import("zod").ZodString>;
5
+ }, import("zod/v4/core").$strip>, import("zod").ZodTransform<import("../types.js").BaseConfig, {
6
+ siteUrl: string;
7
+ root?: string | undefined;
8
+ config?: string | undefined;
9
+ }>>;
@@ -0,0 +1,2 @@
1
+ import { BaseOptionsSchema, resolveBaseConfig } from "./shared.schema.js";
2
+ export const CheckConfigSchema = BaseOptionsSchema.transform((options) => resolveBaseConfig(options));
@@ -0,0 +1,11 @@
1
+ import { z as Zod } from "zod";
2
+ import type { BaseConfig } from "../types.js";
3
+ export declare function isPathWithinRoot(rootPath: string, filePath: string): boolean;
4
+ export declare const BaseOptionsSchema: Zod.ZodObject<{
5
+ siteUrl: Zod.ZodPipe<Zod.ZodString, Zod.ZodURL>;
6
+ root: Zod.ZodOptional<Zod.ZodString>;
7
+ config: Zod.ZodOptional<Zod.ZodString>;
8
+ }, Zod.core.$strip>;
9
+ type BaseOptions = Zod.output<typeof BaseOptionsSchema>;
10
+ export declare function resolveBaseConfig(options: BaseOptions): BaseConfig;
11
+ export {};
@@ -0,0 +1,45 @@
1
+ import NodePath from "node:path";
2
+ import { z as Zod } from "zod";
3
+ import { isRelativePathEscapingRoot } from "../../../shared/utils/path.js";
4
+ export function isPathWithinRoot(rootPath, filePath) {
5
+ const resolvedRoot = NodePath.resolve(rootPath);
6
+ const resolvedPath = NodePath.resolve(resolvedRoot, filePath);
7
+ const relativePath = NodePath.relative(resolvedRoot, resolvedPath);
8
+ return !isRelativePathEscapingRoot(relativePath);
9
+ }
10
+ export const BaseOptionsSchema = Zod.object({
11
+ siteUrl: Zod.string({ error: "Missing required option: --site-url <url>" })
12
+ .trim()
13
+ .min(1, { error: "Missing required option: --site-url <url>" })
14
+ .pipe(Zod.url({
15
+ error: "Invalid option: --site-url must be a valid URL",
16
+ })),
17
+ root: Zod.string().trim().min(1).optional(),
18
+ config: Zod.string().trim().min(1).optional(),
19
+ }).superRefine((options, context) => {
20
+ if (!options.config) {
21
+ return;
22
+ }
23
+ const rootPath = NodePath.resolve(options.root ?? process.cwd());
24
+ if (!isPathWithinRoot(rootPath, options.config)) {
25
+ context.addIssue({
26
+ code: "custom",
27
+ path: ["config"],
28
+ message: "Invalid option: --config must resolve within --root",
29
+ });
30
+ }
31
+ });
32
+ export function resolveBaseConfig(options) {
33
+ const rootPath = NodePath.resolve(options.root ?? process.cwd());
34
+ if (options.config) {
35
+ return {
36
+ siteUrl: options.siteUrl,
37
+ rootPath,
38
+ configFile: NodePath.resolve(rootPath, options.config),
39
+ };
40
+ }
41
+ return {
42
+ siteUrl: options.siteUrl,
43
+ rootPath,
44
+ };
45
+ }
@@ -0,0 +1,9 @@
1
+ export type BaseConfig = {
2
+ siteUrl: string;
3
+ rootPath: string;
4
+ configFile?: string;
5
+ };
6
+ export type BuildConfig = BaseConfig & {
7
+ outPath: string;
8
+ };
9
+ export type CheckConfig = BaseConfig;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { RuntimeViteConfig } from "@site-index/vite-runtime";
2
+ import type { BaseConfig } from "./types.js";
3
+ export declare function makeResolvedViteConfig(config: BaseConfig): RuntimeViteConfig;
@@ -0,0 +1,7 @@
1
+ export function makeResolvedViteConfig(config) {
2
+ return {
3
+ root: config.rootPath,
4
+ mode: "production",
5
+ ...(config.configFile ? { configFile: config.configFile } : {}),
6
+ };
7
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function main(argv?: string[]): Promise<void>;
package/dist/main.js ADDED
@@ -0,0 +1,38 @@
1
+ import { cac } from "cac";
2
+ import pkg from "../package.json" with { type: "json" };
3
+ import { initMakeCommand } from "./domains/make/commands/make.command.js";
4
+ import { initBuildCommand } from "./domains/site-indexes/commands/build.command.js";
5
+ import { initCheckCommand } from "./domains/site-indexes/commands/check.command.js";
6
+ import { logger } from "./shared/logging/logger.js";
7
+ function hasCommandToken(argv) {
8
+ return argv.slice(2).some((argument) => !argument.startsWith("-"));
9
+ }
10
+ export async function main(argv = process.argv) {
11
+ try {
12
+ const cli = cac("site-index");
13
+ cli.option("--quiet", "Suppress informational output");
14
+ cli.option("--verbose", "Enable verbose logging");
15
+ initBuildCommand(cli);
16
+ initCheckCommand(cli);
17
+ initMakeCommand(cli);
18
+ cli.help();
19
+ cli.version(pkg.version);
20
+ const parsed = cli.parse(argv, { run: false });
21
+ logger.configure({
22
+ quiet: Boolean(parsed.options.quiet),
23
+ verbose: Boolean(parsed.options.verbose),
24
+ });
25
+ if (parsed.options.help || parsed.options.version) {
26
+ return;
27
+ }
28
+ if (!hasCommandToken(argv)) {
29
+ cli.outputHelp();
30
+ return;
31
+ }
32
+ await cli.runMatchedCommand();
33
+ }
34
+ catch (error) {
35
+ logger.error(error);
36
+ process.exitCode = 1;
37
+ }
38
+ }
@@ -0,0 +1,2 @@
1
+ import { Logger } from "@site-index/observability";
2
+ export declare const logger: Logger;
@@ -0,0 +1,2 @@
1
+ import { Logger } from "@site-index/observability";
2
+ export const logger = new Logger();
@@ -0,0 +1 @@
1
+ export declare function fileExists(filePath: string): Promise<boolean>;
@@ -0,0 +1,16 @@
1
+ import NodeFS from "node:fs/promises";
2
+ export async function fileExists(filePath) {
3
+ try {
4
+ await NodeFS.access(filePath);
5
+ return true;
6
+ }
7
+ catch (error) {
8
+ if (error &&
9
+ typeof error === "object" &&
10
+ "code" in error &&
11
+ error.code === "ENOENT") {
12
+ return false;
13
+ }
14
+ throw error;
15
+ }
16
+ }
@@ -0,0 +1 @@
1
+ export declare function isRelativePathEscapingRoot(relativePath: string): boolean;
@@ -0,0 +1,6 @@
1
+ import NodePath from "node:path";
2
+ export function isRelativePathEscapingRoot(relativePath) {
3
+ return (relativePath === ".." ||
4
+ relativePath.startsWith(`..${NodePath.sep}`) ||
5
+ NodePath.isAbsolute(relativePath));
6
+ }
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "site-index",
3
+ "version": "0.1.0",
4
+ "description": "CLI for generating sitemap and robots.txt artifacts from site-index modules.",
5
+ "keywords": [
6
+ "site-index",
7
+ "sitemap",
8
+ "robots",
9
+ "seo",
10
+ "vite",
11
+ "cli"
12
+ ],
13
+ "homepage": "https://github.com/Deasilsoft/site-index/tree/main/packages/site-index",
14
+ "bugs": {
15
+ "url": "https://github.com/Deasilsoft/site-index/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/Deasilsoft/site-index.git",
20
+ "directory": "packages/site-index"
21
+ },
22
+ "license": "MIT",
23
+ "author": "Deasilsoft <contact@deasilsoft.com>",
24
+ "type": "module",
25
+ "bin": {
26
+ "site-index": "dist/bin.js"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "!dist/**/*.tsbuildinfo"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsc -b",
34
+ "postbuild": "npm run build:executable && npm run build:templates",
35
+ "build:executable": "chmod +x ./dist/bin.js",
36
+ "build:templates": "mkdir -p dist/domains/make/templates && cp -R src/domains/make/templates/. dist/domains/make/templates/",
37
+ "clean": "rm -rf dist coverage",
38
+ "precli": "npm run clean && npm run build",
39
+ "cli": "node ./dist/bin.js",
40
+ "prepack": "npm run clean && npm run build",
41
+ "pretest": "npm pack --dry-run",
42
+ "test": "vitest run --coverage",
43
+ "pretypecheck": "npm run build",
44
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json"
45
+ },
46
+ "dependencies": {
47
+ "@site-index/core": "0.1.0",
48
+ "@site-index/observability": "0.1.0",
49
+ "@site-index/vite-runtime": "0.1.0",
50
+ "cac": "^7.0.0",
51
+ "vite": "^8.0.12",
52
+ "zod": "^4.4.3"
53
+ },
54
+ "devDependencies": {
55
+ "@vitest/coverage-v8": "^4.1.6",
56
+ "vitest": "^4.1.6"
57
+ },
58
+ "engines": {
59
+ "node": ">=22"
60
+ }
61
+ }