@rm-industries/create-forge 0.3.0-alpha.0 → 0.4.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +4 -2
- package/dist/index.mjs.map +1 -1
- package/dist/template/.gitignore.template +261 -0
- package/dist/template/.lighthouserc.json +28 -0
- package/dist/template/README.md +79 -22
- package/dist/template/audit-ci.jsonc +20 -0
- package/dist/template/cspell.config.ts +18 -2
- package/dist/template/docs/accessibility-checklist.md +50 -0
- package/dist/template/docs/performance-budget.md +68 -0
- package/dist/template/package-lock.json +5666 -381
- package/dist/template/package.json +15 -3
- package/dist/template/playwright.config.ts +9 -3
- package/dist/template/scripts/validate-build.test.ts +58 -0
- package/dist/template/scripts/validate-build.ts +64 -0
- package/dist/template/src/config/content-models/registry.test.ts +14 -0
- package/dist/template/src/config/site.test.ts +13 -0
- package/dist/template/src/content.config.test.ts +8 -0
- package/dist/template/src/integrations/sveltia/previews.test.ts +23 -0
- package/dist/template/src/lib/articles.test.ts +43 -0
- package/dist/template/src/lib/articles.ts +1 -1
- package/dist/template/src/lib/navigation.test.ts +2 -0
- package/dist/template/src/styles/global.css +7 -1
- package/dist/template/stylelint.config.ts +2 -0
- package/dist/template/tests/accessibility.spec.ts +56 -10
- package/dist/template/tests/navigation.spec.ts +43 -0
- package/dist/template/tests/routes.spec.ts +19 -12
- package/dist/template/vitest.config.ts +18 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -260,9 +260,10 @@ const listTemplateFiles = async (root, directory = root) => {
|
|
|
260
260
|
continue;
|
|
261
261
|
}
|
|
262
262
|
if (!entry.isFile()) throw new MaterializationError(`Template entry ${source} is not a regular file.`);
|
|
263
|
+
const relativePath = relative(root, source);
|
|
263
264
|
files.push({
|
|
264
265
|
source,
|
|
265
|
-
relativePath:
|
|
266
|
+
relativePath: relativePath === ".gitignore.template" ? ".gitignore" : relativePath,
|
|
266
267
|
mode: (await stat(source)).mode
|
|
267
268
|
});
|
|
268
269
|
}
|
|
@@ -280,10 +281,11 @@ const ensureDirectory = async (directory, created) => {
|
|
|
280
281
|
await mkdir(directory);
|
|
281
282
|
created.add(directory);
|
|
282
283
|
};
|
|
284
|
+
const quoteTypeScriptString = (value) => `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029")}'`;
|
|
283
285
|
const replaceToken = (source, token, value, field) => {
|
|
284
286
|
const quotedToken = `'${token}'`;
|
|
285
287
|
if (!source.includes(quotedToken)) throw new Error(`Packaged template is missing the ${field} token.`);
|
|
286
|
-
return source.replaceAll(quotedToken,
|
|
288
|
+
return source.replaceAll(quotedToken, quoteTypeScriptString(value));
|
|
287
289
|
};
|
|
288
290
|
const customizeTemplate = async (destination, options) => {
|
|
289
291
|
const packagePath = join(destination, "package.json");
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/prompts.ts","../src/options.ts","../src/validation.ts","../src/resolve-options.ts","../src/cli.ts","../src/template-tokens.ts","../src/materialize.ts","../src/process.ts","../src/reporter.ts","../src/index.ts"],"sourcesContent":["import { confirm, isCancel, text } from '@clack/prompts';\n\nexport type TextPrompt = {\n message: string;\n initialValue?: string;\n placeholder?: string;\n validate?: (value: string | undefined) => string | Error | undefined;\n};\n\nexport type ConfirmPrompt = { message: string; initialValue: boolean };\n\nexport type PromptAdapter = {\n text(options: TextPrompt): Promise<string | symbol>;\n confirm(options: ConfirmPrompt): Promise<boolean | symbol>;\n isCancel(value: unknown): boolean;\n};\n\nexport const clackPrompts: PromptAdapter = {\n text: (options) =>\n text({\n message: options.message,\n ...(options.initialValue === undefined ? {} : { initialValue: options.initialValue }),\n ...(options.placeholder === undefined ? {} : { placeholder: options.placeholder }),\n ...(options.validate === undefined ? {} : { validate: options.validate }),\n }),\n confirm,\n isCancel,\n};\n","export type GeneratorOptions = {\n destination: string;\n packageName: string;\n siteName: string;\n description: string;\n author: string;\n url: string;\n repository: string;\n install: boolean;\n git: boolean;\n};\n\nexport type ProvidedOptions = {\n [Key in keyof GeneratorOptions]?: GeneratorOptions[Key] | undefined;\n} & { yes: boolean };\n\nexport const defaultDescription = 'A content-driven website built with Forge';\nexport const defaultUrl = 'https://example.com';\n\nexport const derivePackageName = (destination: string) => {\n return basename(resolve(destination)) || 'forge-site';\n};\n\nexport const humanizePackageName = (packageName: string) => {\n const unscopedName = packageName.split('/').at(-1) ?? packageName;\n return unscopedName\n .split(/[-_\\s]+/u)\n .filter(Boolean)\n .map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)\n .join(' ');\n};\nimport { basename, resolve } from 'node:path';\n","import validateNpmPackageName from 'validate-npm-package-name';\n\nexport class InputError extends Error {}\n\nconst invalid = (option: string, value: string, reason: string): never => {\n throw new InputError(`Invalid ${option} value ${JSON.stringify(value)}: ${reason}`);\n};\n\nexport const validateDestination = (value: string) => {\n const destination = value.trim();\n if (!destination) invalid('destination', value, 'provide a non-empty path');\n if (destination.includes('\\0')) invalid('destination', value, 'paths cannot contain null bytes');\n return destination;\n};\n\nexport const validatePackageName = (value: string) => {\n const packageName = value.trim();\n const result = validateNpmPackageName(packageName);\n if (!result.validForNewPackages) {\n const reason = [...(result.errors ?? []), ...(result.warnings ?? [])].join('; ');\n invalid('--name', value, reason || 'provide a valid npm package name');\n }\n return packageName;\n};\n\nexport const validatePlainText = (option: string, value: string, required = false) => {\n const text = value.trim();\n if (required && !text) invalid(option, value, 'provide non-empty text');\n if (/\\p{Cc}/u.test(text)) invalid(option, value, 'control characters are not allowed');\n return text;\n};\n\nexport const validateUrl = (value: string) => {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return invalid('--url', value, 'provide an absolute HTTP(S) URL');\n }\n if (!['http:', 'https:'].includes(url.protocol)) {\n invalid('--url', value, 'provide an absolute HTTP(S) URL');\n }\n return url.toString();\n};\n\nexport const validateRepository = (value: string) => {\n const repository = value.trim();\n if (!repository) return repository;\n if (!/^[\\w.-]+\\/[\\w.-]+$/u.test(repository)) {\n invalid('--repository', value, 'use the GitHub owner/repository format or leave it empty');\n }\n return repository;\n};\n","import {\n defaultDescription,\n defaultUrl,\n derivePackageName,\n humanizePackageName,\n type GeneratorOptions,\n type ProvidedOptions,\n} from './options';\nimport type { PromptAdapter, TextPrompt } from './prompts';\nimport {\n InputError,\n validateDestination,\n validatePackageName,\n validatePlainText,\n validateRepository,\n validateUrl,\n} from './validation';\n\nexport class CancellationError extends Error {}\n\ntype ResolutionContext = { interactive: boolean; prompts: PromptAdapter };\n\nconst textValidation = (validator: (value: string) => string) => (value: string | undefined) => {\n try {\n validator(value ?? '');\n return undefined;\n } catch (error) {\n return error instanceof Error ? error.message : 'Invalid value';\n }\n};\n\nconst promptText = async (prompts: PromptAdapter, options: TextPrompt) => {\n const value = await prompts.text(options);\n if (prompts.isCancel(value)) throw new CancellationError('Project creation cancelled.');\n return String(value);\n};\n\nconst promptConfirm = async (prompts: PromptAdapter, message: string) => {\n const value = await prompts.confirm({ message, initialValue: true });\n if (prompts.isCancel(value)) throw new CancellationError('Project creation cancelled.');\n return Boolean(value);\n};\n\nconst hasEveryExplicitValue = (options: ProvidedOptions) =>\n options.destination !== undefined &&\n options.packageName !== undefined &&\n options.siteName !== undefined &&\n options.description !== undefined &&\n options.author !== undefined &&\n options.url !== undefined &&\n options.repository !== undefined &&\n options.install !== undefined &&\n options.git !== undefined;\n\nexport const resolveOptions = async (\n provided: ProvidedOptions,\n { interactive, prompts }: ResolutionContext,\n): Promise<GeneratorOptions> => {\n const useDefaults = provided.yes;\n if (!interactive && !useDefaults && !hasEveryExplicitValue(provided)) {\n throw new InputError(\n 'Non-interactive use requires --yes or explicit values for every generator option. Run create-forge --help for details.',\n );\n }\n\n const destination = validateDestination(\n provided.destination ??\n (useDefaults\n ? '.'\n : await promptText(prompts, {\n message: 'Where should we create your project?',\n placeholder: '.',\n initialValue: '.',\n validate: textValidation(validateDestination),\n })),\n );\n const derivedPackageName = derivePackageName(destination);\n const packageName = validatePackageName(\n provided.packageName ??\n (useDefaults\n ? derivedPackageName\n : await promptText(prompts, {\n message: 'What should the npm package be named?',\n initialValue: derivedPackageName,\n validate: textValidation(validatePackageName),\n })),\n );\n const defaultSiteName = humanizePackageName(packageName);\n const siteName = validatePlainText(\n '--site-name',\n provided.siteName ??\n (useDefaults\n ? defaultSiteName\n : await promptText(prompts, {\n message: 'What should the site be called?',\n initialValue: defaultSiteName,\n validate: textValidation((value) => validatePlainText('--site-name', value, true)),\n })),\n true,\n );\n const description = validatePlainText(\n '--description',\n provided.description ??\n (useDefaults\n ? defaultDescription\n : await promptText(prompts, {\n message: 'How would you describe the site?',\n initialValue: defaultDescription,\n validate: textValidation((value) => validatePlainText('--description', value)),\n })),\n );\n const author = validatePlainText(\n '--author',\n provided.author ??\n (useDefaults\n ? ''\n : await promptText(prompts, {\n message: 'Who is the site author?',\n placeholder: 'Optional',\n validate: textValidation((value) => validatePlainText('--author', value)),\n })),\n );\n const url = validateUrl(\n provided.url ??\n (useDefaults\n ? defaultUrl\n : await promptText(prompts, {\n message: 'What is the canonical site URL?',\n initialValue: defaultUrl,\n validate: textValidation(validateUrl),\n })),\n );\n const repository = validateRepository(\n provided.repository ??\n (useDefaults\n ? ''\n : await promptText(prompts, {\n message: 'What is the GitHub repository?',\n placeholder: 'owner/repository (optional)',\n validate: textValidation(validateRepository),\n })),\n );\n const install = provided.install ?? (useDefaults ? true : await promptConfirm(prompts, 'Install dependencies?'));\n const git = provided.git ?? (useDefaults ? true : await promptConfirm(prompts, 'Initialize a Git repository?'));\n\n return { destination, packageName, siteName, description, author, url, repository, install, git };\n};\n","import { Command, CommanderError, Option } from 'commander';\n\nimport type { GeneratorOptions, ProvidedOptions } from './options';\nimport { clackPrompts, type PromptAdapter } from './prompts';\nimport { CancellationError, resolveOptions } from './resolve-options';\n\nexport type CliResult = { output: string; exitCode: number; options?: GeneratorOptions };\nexport type CliContext = { interactive?: boolean; prompts?: PromptAdapter };\n\nconst hasBoth = (args: readonly string[], positive: string, negative: string) =>\n args.includes(positive) && args.includes(negative);\n\nconst parseArguments = (args: readonly string[], version: string) => {\n let output = '';\n const program = new Command()\n .name('create-forge')\n .description('Create a content-driven Forge website')\n .argument('[destination]', 'directory where the project will be created')\n .option('--name <name>', 'npm package name (defaults to the directory basename)')\n .option('--site-name <name>', 'site name')\n .option('--description <text>', 'site description')\n .option('--author <name>', 'site author')\n .option('--url <url>', 'absolute canonical HTTP(S) URL')\n .option('--repository <owner/repository>', 'GitHub repository or an empty value')\n .addOption(new Option('--install', 'install dependencies'))\n .addOption(new Option('--no-install', 'do not install dependencies'))\n .addOption(new Option('--git', 'initialize a Git repository'))\n .addOption(new Option('--no-git', 'do not initialize a Git repository'))\n .option('-y, --yes', 'use documented defaults without prompting', false)\n .version(version, '-v, --version', 'show the installed package version')\n .helpOption('-h, --help', 'show this help message')\n .showHelpAfterError()\n .allowExcessArguments(false)\n .exitOverride()\n .configureOutput({\n writeOut: (value) => {\n output += value;\n },\n writeErr: (value) => {\n output += value;\n },\n });\n\n try {\n program.parse(args, { from: 'user' });\n } catch (error) {\n if (error instanceof CommanderError) return { output, exitCode: error.exitCode } as const;\n throw error;\n }\n\n if (hasBoth(args, '--install', '--no-install')) {\n return { output: 'error: --install cannot be used with --no-install\\n', exitCode: 1 } as const;\n }\n if (hasBoth(args, '--git', '--no-git')) {\n return { output: 'error: --git cannot be used with --no-git\\n', exitCode: 1 } as const;\n }\n\n const raw = program.opts();\n const provided: ProvidedOptions = {\n destination: program.args[0],\n packageName: raw.name,\n siteName: raw.siteName,\n description: raw.description,\n author: raw.author,\n url: raw.url,\n repository: raw.repository,\n install: raw.install,\n git: raw.git,\n yes: raw.yes,\n };\n return { provided } as const;\n};\n\nexport const runCli = async (\n args: readonly string[],\n version: string,\n context: CliContext = {},\n): Promise<CliResult> => {\n const parsed = parseArguments(args, version);\n if (!('provided' in parsed)) return parsed;\n\n try {\n const options = await resolveOptions(parsed.provided, {\n interactive: context.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY),\n prompts: context.prompts ?? clackPrompts,\n });\n return { output: '', exitCode: 0, options };\n } catch (error) {\n if (error instanceof CancellationError) return { output: `${error.message}\\n`, exitCode: 1 };\n if (error instanceof Error) return { output: `error: ${error.message}\\n`, exitCode: 1 };\n throw error;\n }\n};\n","const prefix = '__RM_INDUSTRIES_FORGE_TEMPLATE_V1_';\n\nexport const templateTokens = Object.freeze({\n packageName: `${prefix}PACKAGE_NAME__`,\n siteName: `${prefix}SITE_NAME__`,\n description: `${prefix}DESCRIPTION__`,\n author: `${prefix}AUTHOR__`,\n url: `${prefix}URL__`,\n repository: `${prefix}REPOSITORY__`,\n});\n\nexport const templateTokenPrefix = prefix;\n","import {\n chmod,\n copyFile,\n lstat,\n mkdir,\n mkdtemp,\n readdir,\n readFile,\n rm,\n rmdir,\n stat,\n writeFile,\n} from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { dirname, isAbsolute, join, parse, relative, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { GeneratorOptions } from './options';\nimport { templateTokenPrefix, templateTokens } from './template-tokens';\n\nexport class MaterializationError extends Error {}\n\ntype MaterializationContext = {\n templateDirectory: string | URL;\n cwd?: string;\n signal?: AbortSignal;\n confirmOverwrite?: (destination: string) => Promise<boolean>;\n onFileCopied?: (path: string) => void;\n};\n\ntype TemplateFile = { source: string; relativePath: string; mode: number };\n\nconst pathExists = async (path: string) => {\n try {\n await lstat(path);\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false;\n throw error;\n }\n};\n\nconst getPathStat = async (path: string) => {\n try {\n return await lstat(path);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n};\n\nconst assertNotAborted = (signal?: AbortSignal) => {\n if (signal?.aborted) throw new Error('Project creation was interrupted.');\n};\n\nconst resolveTemplateDirectory = (directory: string | URL) =>\n directory instanceof URL ? fileURLToPath(directory) : resolve(directory);\n\nconst resolveDestination = (destination: string, cwd: string) => {\n const segments = destination.split(/[\\\\/]/u);\n if (segments.includes('..')) {\n throw new MaterializationError(\n `Unsafe destination ${JSON.stringify(destination)}: parent traversal is not allowed.`,\n );\n }\n const resolved = isAbsolute(destination) ? resolve(destination) : resolve(cwd, destination);\n if (resolved === parse(resolved).root) {\n throw new MaterializationError(\n `Unsafe destination ${JSON.stringify(destination)}: a filesystem root is not allowed.`,\n );\n }\n return resolved;\n};\n\nconst listTemplateFiles = async (root: string, directory = root): Promise<TemplateFile[]> => {\n const entries = await readdir(directory, { withFileTypes: true });\n const files: TemplateFile[] = [];\n for (const entry of entries) {\n const source = join(directory, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await listTemplateFiles(root, source)));\n continue;\n }\n if (!entry.isFile()) throw new MaterializationError(`Template entry ${source} is not a regular file.`);\n files.push({ source, relativePath: relative(root, source), mode: (await stat(source)).mode });\n }\n return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));\n};\n\nconst isDirectoryEmpty = async (directory: string) => (await readdir(directory)).length === 0;\n\nconst ensureDirectory = async (directory: string, created: Set<string>) => {\n const existing = await getPathStat(directory);\n if (existing) {\n if (existing.isSymbolicLink() || !existing.isDirectory()) {\n throw new Error(`Unsafe destination entry ${directory}: expected a regular directory.`);\n }\n return;\n }\n const parent = dirname(directory);\n if (parent !== directory) await ensureDirectory(parent, created);\n await mkdir(directory);\n created.add(directory);\n};\n\nconst replaceToken = (source: string, token: string, value: string, field: string) => {\n const quotedToken = `'${token}'`;\n if (!source.includes(quotedToken)) throw new Error(`Packaged template is missing the ${field} token.`);\n return source.replaceAll(quotedToken, JSON.stringify(value));\n};\n\nconst customizeTemplate = async (destination: string, options: GeneratorOptions) => {\n const packagePath = join(destination, 'package.json');\n const packageLockPath = join(destination, 'package-lock.json');\n const packageMetadata = JSON.parse(await readFile(packagePath, 'utf8')) as Record<string, unknown>;\n const packageLock = JSON.parse(await readFile(packageLockPath, 'utf8')) as {\n name?: string;\n packages?: Record<string, Record<string, unknown>>;\n };\n if (\n packageMetadata.name !== templateTokens.packageName ||\n packageLock.name !== templateTokens.packageName ||\n packageLock.packages?.['']?.name !== templateTokens.packageName\n ) {\n throw new Error('Packaged template contains unexpected package metadata tokens.');\n }\n packageMetadata.name = options.packageName;\n packageMetadata.version = '0.0.0';\n packageMetadata.private = true;\n packageLock.name = options.packageName;\n if (!packageLock.packages?.['']) throw new Error('Packaged template lockfile is missing its root package metadata.');\n packageLock.packages[''].name = options.packageName;\n packageLock.packages[''].version = '0.0.0';\n await writeFile(packagePath, `${JSON.stringify(packageMetadata, undefined, 2)}\\n`);\n await writeFile(packageLockPath, `${JSON.stringify(packageLock, undefined, 2)}\\n`);\n\n const siteConfigPath = join(destination, 'src', 'config', 'site.ts');\n let siteConfig = await readFile(siteConfigPath, 'utf8');\n siteConfig = replaceToken(siteConfig, templateTokens.siteName, options.siteName, 'site name');\n siteConfig = replaceToken(siteConfig, templateTokens.description, options.description, 'description');\n siteConfig = replaceToken(siteConfig, templateTokens.author, options.author, 'author');\n siteConfig = replaceToken(siteConfig, templateTokens.url, options.url, 'URL');\n siteConfig = replaceToken(siteConfig, templateTokens.repository, options.repository, 'repository');\n if (siteConfig.includes(templateTokenPrefix)) throw new Error('Packaged template contains unresolved tokens.');\n await writeFile(siteConfigPath, siteConfig);\n};\n\nexport const materializeProject = async (options: GeneratorOptions, context: MaterializationContext) => {\n const cwd = context.cwd ?? process.cwd();\n const destination = resolveDestination(options.destination, cwd);\n const templateDirectory = resolveTemplateDirectory(context.templateDirectory);\n const destinationExisted = await pathExists(destination);\n if (destinationExisted) {\n const destinationStat = await lstat(destination);\n if (destinationStat.isSymbolicLink() || !destinationStat.isDirectory()) {\n throw new MaterializationError(`Unsafe destination ${destination}: expected a regular directory.`);\n }\n }\n if (destinationExisted && !(await isDirectoryEmpty(destination))) {\n const confirmed = await context.confirmOverwrite?.(destination);\n if (!confirmed) {\n throw new MaterializationError(`Destination ${destination} is not empty. No files were changed.`);\n }\n }\n\n const files = await listTemplateFiles(templateDirectory);\n const backupDirectory = await mkdtemp(join(tmpdir(), 'create-forge-backup-'));\n const createdFiles = new Set<string>();\n const createdDirectories = new Set<string>();\n const backups = new Map<string, string>();\n\n try {\n assertNotAborted(context.signal);\n await ensureDirectory(destination, createdDirectories);\n for (const file of files) {\n assertNotAborted(context.signal);\n const target = join(destination, file.relativePath);\n const targetRelative = relative(destination, target);\n if (targetRelative.startsWith('..') || isAbsolute(targetRelative))\n throw new Error(`Unsafe template path ${file.relativePath}.`);\n await ensureDirectory(dirname(target), createdDirectories);\n const targetStat = await getPathStat(target);\n if (targetStat) {\n if (targetStat.isSymbolicLink() || !targetStat.isFile()) {\n throw new Error(`Unsafe destination entry ${target}: expected a regular file.`);\n }\n const backup = join(backupDirectory, file.relativePath);\n await mkdir(dirname(backup), { recursive: true });\n await copyFile(target, backup);\n backups.set(target, backup);\n } else {\n createdFiles.add(target);\n }\n await copyFile(file.source, target);\n await chmod(target, file.mode);\n context.onFileCopied?.(target);\n }\n assertNotAborted(context.signal);\n await customizeTemplate(destination, options);\n const tokenBytes = Buffer.from(templateTokenPrefix);\n for (const file of files) {\n assertNotAborted(context.signal);\n if ((await readFile(join(destination, file.relativePath))).includes(tokenBytes)) {\n throw new Error(`Generated file ${file.relativePath} contains an unresolved template token.`);\n }\n }\n return { destination, filesCopied: files.length };\n } catch (error) {\n const cleanupErrors: unknown[] = [];\n for (const [target, backup] of backups) {\n try {\n await copyFile(backup, target);\n } catch (cleanupError) {\n cleanupErrors.push(cleanupError);\n }\n }\n for (const file of createdFiles) {\n try {\n await rm(file, { force: true });\n } catch (cleanupError) {\n cleanupErrors.push(cleanupError);\n }\n }\n for (const directory of [...createdDirectories].sort((left, right) => right.length - left.length)) {\n try {\n await rmdir(directory);\n } catch (cleanupError) {\n if (!['ENOENT', 'ENOTEMPTY'].includes((cleanupError as NodeJS.ErrnoException).code ?? '')) {\n cleanupErrors.push(cleanupError);\n }\n }\n }\n const reason = error instanceof Error ? error.message : String(error);\n const recovery = cleanupErrors.length\n ? `Recovery: review ${destination}; some changes could not be rolled back automatically.`\n : `Recovery: changes from this invocation were rolled back; resolve the error and retry.`;\n throw new MaterializationError(`${reason} ${recovery}`, { cause: error });\n } finally {\n await rm(backupDirectory, { recursive: true, force: true });\n }\n};\n","import { spawn } from 'node:child_process';\n\nimport type { GeneratorOptions } from './options';\n\nexport class ProcessStepError extends Error {\n readonly exitCode: number;\n\n constructor(message: string, exitCode = 1, options?: ErrorOptions) {\n super(message, options);\n this.exitCode = exitCode;\n }\n}\n\nexport type Command = {\n executable: string;\n arguments: readonly string[];\n cwd: string;\n signal?: AbortSignal;\n environment?: NodeJS.ProcessEnv;\n};\n\nexport type CommandExecutor = (command: Command) => Promise<void>;\n\nconst signalExitCode = (signal: NodeJS.Signals | null) => {\n if (signal === 'SIGINT') return 130;\n if (signal === 'SIGTERM') return 143;\n return 1;\n};\n\nexport const executeCommand: CommandExecutor = ({ executable, arguments: args, cwd, signal, environment }) =>\n new Promise((resolve, reject) => {\n const child = spawn(executable, args, {\n cwd,\n stdio: 'inherit',\n ...(signal ? { signal } : {}),\n ...(environment ? { env: environment } : {}),\n });\n\n child.once('error', (error) => {\n const interrupted = signal?.aborted;\n reject(\n new ProcessStepError(\n interrupted\n ? `${executable} was interrupted. The generated project remains at ${cwd}.`\n : `Could not start ${executable}: ${error.message}. The generated project remains at ${cwd}.`,\n interrupted ? 130 : 1,\n { cause: error },\n ),\n );\n });\n child.once('close', (code, processSignal) => {\n if (code === 0) {\n resolve();\n return;\n }\n const detail = processSignal ? `signal ${processSignal}` : `exit code ${code ?? 'unknown'}`;\n reject(\n new ProcessStepError(\n `${executable} failed with ${detail}. The generated project remains at ${cwd}; review the output above and retry the command there.`,\n processSignal ? signalExitCode(processSignal) : (code ?? 1),\n ),\n );\n });\n });\n\ntype ProjectSetupContext = {\n destination: string;\n signal?: AbortSignal;\n execute?: CommandExecutor;\n environment?: NodeJS.ProcessEnv;\n};\n\nexport const runProjectSetup = async (options: GeneratorOptions, context: ProjectSetupContext) => {\n const execute = context.execute ?? executeCommand;\n const command = (executable: string, args: readonly string[]) =>\n execute({\n executable,\n arguments: args,\n cwd: context.destination,\n ...(context.signal ? { signal: context.signal } : {}),\n ...(context.environment ? { environment: context.environment } : {}),\n });\n\n if (options.install) await command('npm', ['install']);\n if (options.git) await command('git', ['init', '--initial-branch=main']);\n};\n","import { isAbsolute, relative } from 'node:path';\n\nimport type { GeneratorOptions } from './options';\n\ntype CompletionContext = {\n destination: string;\n cwd?: string;\n color?: boolean;\n};\n\nconst ansi = {\n bold: (value: string) => `\\u001B[1m${value}\\u001B[22m`,\n green: (value: string) => `\\u001B[32m${value}\\u001B[39m`,\n};\n\nconst commandPath = (path: string) => {\n if (/^[\\w./@-]+$/u.test(path)) return path;\n return `\"${path.replaceAll('\"', '\\\\\"')}\"`;\n};\n\nconst displayDestination = (destination: string, cwd: string) => {\n const path = relative(cwd, destination);\n if (!path) return '.';\n return isAbsolute(path) ? destination : path;\n};\n\nexport const supportsColor = (environment: NodeJS.ProcessEnv = process.env, terminal = Boolean(process.stdout.isTTY)) =>\n terminal && environment.NO_COLOR === undefined;\n\nexport const formatCompletion = (options: GeneratorOptions, context: CompletionContext) => {\n const cwd = context.cwd ?? process.cwd();\n const color = context.color ?? supportsColor();\n const decorate = color ? ansi : { bold: (value: string) => value, green: (value: string) => value };\n const destination = displayDestination(context.destination, cwd);\n const commands = [\n ...(destination === '.' ? [] : [`cd ${commandPath(destination)}`]),\n ...(options.install ? [] : ['npm install']),\n 'npm run dev',\n ];\n\n return `${decorate.green('✓')} Created ${options.siteName} in ${destination}\\n\\n${decorate.bold('Setup')}\\n Dependencies: ${options.install ? 'installed' : 'skipped'}\\n Git repository: ${options.git ? 'initialized' : 'skipped'}\\n\\n${decorate.bold('Next steps')}\\n${commands.map((command) => ` ${command}`).join('\\n')}\\n`;\n};\n","#!/usr/bin/env node\n\nimport { readFile } from 'node:fs/promises';\n\nimport { confirm, isCancel } from '@clack/prompts';\n\nimport { runCli } from './cli';\nimport { materializeProject } from './materialize';\nimport { ProcessStepError, runProjectSetup } from './process';\nimport { formatCompletion } from './reporter';\n\ntype PackageMetadata = {\n version: string;\n};\n\nconst packageJson = new URL('../package.json', import.meta.url);\nconst metadata = JSON.parse(await readFile(packageJson, 'utf8')) as PackageMetadata;\nconst interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);\nconst result = await runCli(process.argv.slice(2), metadata.version, { interactive });\n\nif (result.options) {\n const controller = new AbortController();\n let interruptedExitCode: number | undefined;\n const interrupt = () => {\n interruptedExitCode = 130;\n controller.abort();\n };\n const terminate = () => {\n interruptedExitCode = 143;\n controller.abort();\n };\n process.once('SIGINT', interrupt);\n process.once('SIGTERM', terminate);\n try {\n const materialized = await materializeProject(result.options, {\n templateDirectory: new URL('./template/', import.meta.url),\n signal: controller.signal,\n ...(interactive\n ? {\n confirmOverwrite: async (destination: string) => {\n const answer = await confirm({\n message: `${destination} is not empty. Continue and overwrite conflicting template files?`,\n initialValue: false,\n });\n return !isCancel(answer) && answer;\n },\n }\n : {}),\n });\n await runProjectSetup(result.options, {\n destination: materialized.destination,\n signal: controller.signal,\n });\n result.output = formatCompletion(result.options, { destination: materialized.destination });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n result.output = `error: ${message}\\n`;\n result.exitCode = interruptedExitCode ?? (error instanceof ProcessStepError ? error.exitCode : 1);\n } finally {\n process.off('SIGINT', interrupt);\n process.off('SIGTERM', terminate);\n }\n}\n\nprocess.stdout.write(result.output);\nprocess.exitCode = result.exitCode;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/prompts.ts","../src/options.ts","../src/validation.ts","../src/resolve-options.ts","../src/cli.ts","../src/template-tokens.ts","../src/materialize.ts","../src/process.ts","../src/reporter.ts","../src/index.ts"],"sourcesContent":["import { confirm, isCancel, text } from '@clack/prompts';\n\nexport type TextPrompt = {\n message: string;\n initialValue?: string;\n placeholder?: string;\n validate?: (value: string | undefined) => string | Error | undefined;\n};\n\nexport type ConfirmPrompt = { message: string; initialValue: boolean };\n\nexport type PromptAdapter = {\n text(options: TextPrompt): Promise<string | symbol>;\n confirm(options: ConfirmPrompt): Promise<boolean | symbol>;\n isCancel(value: unknown): boolean;\n};\n\nexport const clackPrompts: PromptAdapter = {\n text: (options) =>\n text({\n message: options.message,\n ...(options.initialValue === undefined ? {} : { initialValue: options.initialValue }),\n ...(options.placeholder === undefined ? {} : { placeholder: options.placeholder }),\n ...(options.validate === undefined ? {} : { validate: options.validate }),\n }),\n confirm,\n isCancel,\n};\n","export type GeneratorOptions = {\n destination: string;\n packageName: string;\n siteName: string;\n description: string;\n author: string;\n url: string;\n repository: string;\n install: boolean;\n git: boolean;\n};\n\nexport type ProvidedOptions = {\n [Key in keyof GeneratorOptions]?: GeneratorOptions[Key] | undefined;\n} & { yes: boolean };\n\nexport const defaultDescription = 'A content-driven website built with Forge';\nexport const defaultUrl = 'https://example.com';\n\nexport const derivePackageName = (destination: string) => {\n return basename(resolve(destination)) || 'forge-site';\n};\n\nexport const humanizePackageName = (packageName: string) => {\n const unscopedName = packageName.split('/').at(-1) ?? packageName;\n return unscopedName\n .split(/[-_\\s]+/u)\n .filter(Boolean)\n .map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)\n .join(' ');\n};\nimport { basename, resolve } from 'node:path';\n","import validateNpmPackageName from 'validate-npm-package-name';\n\nexport class InputError extends Error {}\n\nconst invalid = (option: string, value: string, reason: string): never => {\n throw new InputError(`Invalid ${option} value ${JSON.stringify(value)}: ${reason}`);\n};\n\nexport const validateDestination = (value: string) => {\n const destination = value.trim();\n if (!destination) invalid('destination', value, 'provide a non-empty path');\n if (destination.includes('\\0')) invalid('destination', value, 'paths cannot contain null bytes');\n return destination;\n};\n\nexport const validatePackageName = (value: string) => {\n const packageName = value.trim();\n const result = validateNpmPackageName(packageName);\n if (!result.validForNewPackages) {\n const reason = [...(result.errors ?? []), ...(result.warnings ?? [])].join('; ');\n invalid('--name', value, reason || 'provide a valid npm package name');\n }\n return packageName;\n};\n\nexport const validatePlainText = (option: string, value: string, required = false) => {\n const text = value.trim();\n if (required && !text) invalid(option, value, 'provide non-empty text');\n if (/\\p{Cc}/u.test(text)) invalid(option, value, 'control characters are not allowed');\n return text;\n};\n\nexport const validateUrl = (value: string) => {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return invalid('--url', value, 'provide an absolute HTTP(S) URL');\n }\n if (!['http:', 'https:'].includes(url.protocol)) {\n invalid('--url', value, 'provide an absolute HTTP(S) URL');\n }\n return url.toString();\n};\n\nexport const validateRepository = (value: string) => {\n const repository = value.trim();\n if (!repository) return repository;\n if (!/^[\\w.-]+\\/[\\w.-]+$/u.test(repository)) {\n invalid('--repository', value, 'use the GitHub owner/repository format or leave it empty');\n }\n return repository;\n};\n","import {\n defaultDescription,\n defaultUrl,\n derivePackageName,\n humanizePackageName,\n type GeneratorOptions,\n type ProvidedOptions,\n} from './options';\nimport type { PromptAdapter, TextPrompt } from './prompts';\nimport {\n InputError,\n validateDestination,\n validatePackageName,\n validatePlainText,\n validateRepository,\n validateUrl,\n} from './validation';\n\nexport class CancellationError extends Error {}\n\ntype ResolutionContext = { interactive: boolean; prompts: PromptAdapter };\n\nconst textValidation = (validator: (value: string) => string) => (value: string | undefined) => {\n try {\n validator(value ?? '');\n return undefined;\n } catch (error) {\n return error instanceof Error ? error.message : 'Invalid value';\n }\n};\n\nconst promptText = async (prompts: PromptAdapter, options: TextPrompt) => {\n const value = await prompts.text(options);\n if (prompts.isCancel(value)) throw new CancellationError('Project creation cancelled.');\n return String(value);\n};\n\nconst promptConfirm = async (prompts: PromptAdapter, message: string) => {\n const value = await prompts.confirm({ message, initialValue: true });\n if (prompts.isCancel(value)) throw new CancellationError('Project creation cancelled.');\n return Boolean(value);\n};\n\nconst hasEveryExplicitValue = (options: ProvidedOptions) =>\n options.destination !== undefined &&\n options.packageName !== undefined &&\n options.siteName !== undefined &&\n options.description !== undefined &&\n options.author !== undefined &&\n options.url !== undefined &&\n options.repository !== undefined &&\n options.install !== undefined &&\n options.git !== undefined;\n\nexport const resolveOptions = async (\n provided: ProvidedOptions,\n { interactive, prompts }: ResolutionContext,\n): Promise<GeneratorOptions> => {\n const useDefaults = provided.yes;\n if (!interactive && !useDefaults && !hasEveryExplicitValue(provided)) {\n throw new InputError(\n 'Non-interactive use requires --yes or explicit values for every generator option. Run create-forge --help for details.',\n );\n }\n\n const destination = validateDestination(\n provided.destination ??\n (useDefaults\n ? '.'\n : await promptText(prompts, {\n message: 'Where should we create your project?',\n placeholder: '.',\n initialValue: '.',\n validate: textValidation(validateDestination),\n })),\n );\n const derivedPackageName = derivePackageName(destination);\n const packageName = validatePackageName(\n provided.packageName ??\n (useDefaults\n ? derivedPackageName\n : await promptText(prompts, {\n message: 'What should the npm package be named?',\n initialValue: derivedPackageName,\n validate: textValidation(validatePackageName),\n })),\n );\n const defaultSiteName = humanizePackageName(packageName);\n const siteName = validatePlainText(\n '--site-name',\n provided.siteName ??\n (useDefaults\n ? defaultSiteName\n : await promptText(prompts, {\n message: 'What should the site be called?',\n initialValue: defaultSiteName,\n validate: textValidation((value) => validatePlainText('--site-name', value, true)),\n })),\n true,\n );\n const description = validatePlainText(\n '--description',\n provided.description ??\n (useDefaults\n ? defaultDescription\n : await promptText(prompts, {\n message: 'How would you describe the site?',\n initialValue: defaultDescription,\n validate: textValidation((value) => validatePlainText('--description', value)),\n })),\n );\n const author = validatePlainText(\n '--author',\n provided.author ??\n (useDefaults\n ? ''\n : await promptText(prompts, {\n message: 'Who is the site author?',\n placeholder: 'Optional',\n validate: textValidation((value) => validatePlainText('--author', value)),\n })),\n );\n const url = validateUrl(\n provided.url ??\n (useDefaults\n ? defaultUrl\n : await promptText(prompts, {\n message: 'What is the canonical site URL?',\n initialValue: defaultUrl,\n validate: textValidation(validateUrl),\n })),\n );\n const repository = validateRepository(\n provided.repository ??\n (useDefaults\n ? ''\n : await promptText(prompts, {\n message: 'What is the GitHub repository?',\n placeholder: 'owner/repository (optional)',\n validate: textValidation(validateRepository),\n })),\n );\n const install = provided.install ?? (useDefaults ? true : await promptConfirm(prompts, 'Install dependencies?'));\n const git = provided.git ?? (useDefaults ? true : await promptConfirm(prompts, 'Initialize a Git repository?'));\n\n return { destination, packageName, siteName, description, author, url, repository, install, git };\n};\n","import { Command, CommanderError, Option } from 'commander';\n\nimport type { GeneratorOptions, ProvidedOptions } from './options';\nimport { clackPrompts, type PromptAdapter } from './prompts';\nimport { CancellationError, resolveOptions } from './resolve-options';\n\nexport type CliResult = { output: string; exitCode: number; options?: GeneratorOptions };\nexport type CliContext = { interactive?: boolean; prompts?: PromptAdapter };\n\nconst hasBoth = (args: readonly string[], positive: string, negative: string) =>\n args.includes(positive) && args.includes(negative);\n\nconst parseArguments = (args: readonly string[], version: string) => {\n let output = '';\n const program = new Command()\n .name('create-forge')\n .description('Create a content-driven Forge website')\n .argument('[destination]', 'directory where the project will be created')\n .option('--name <name>', 'npm package name (defaults to the directory basename)')\n .option('--site-name <name>', 'site name')\n .option('--description <text>', 'site description')\n .option('--author <name>', 'site author')\n .option('--url <url>', 'absolute canonical HTTP(S) URL')\n .option('--repository <owner/repository>', 'GitHub repository or an empty value')\n .addOption(new Option('--install', 'install dependencies'))\n .addOption(new Option('--no-install', 'do not install dependencies'))\n .addOption(new Option('--git', 'initialize a Git repository'))\n .addOption(new Option('--no-git', 'do not initialize a Git repository'))\n .option('-y, --yes', 'use documented defaults without prompting', false)\n .version(version, '-v, --version', 'show the installed package version')\n .helpOption('-h, --help', 'show this help message')\n .showHelpAfterError()\n .allowExcessArguments(false)\n .exitOverride()\n .configureOutput({\n writeOut: (value) => {\n output += value;\n },\n writeErr: (value) => {\n output += value;\n },\n });\n\n try {\n program.parse(args, { from: 'user' });\n } catch (error) {\n if (error instanceof CommanderError) return { output, exitCode: error.exitCode } as const;\n throw error;\n }\n\n if (hasBoth(args, '--install', '--no-install')) {\n return { output: 'error: --install cannot be used with --no-install\\n', exitCode: 1 } as const;\n }\n if (hasBoth(args, '--git', '--no-git')) {\n return { output: 'error: --git cannot be used with --no-git\\n', exitCode: 1 } as const;\n }\n\n const raw = program.opts();\n const provided: ProvidedOptions = {\n destination: program.args[0],\n packageName: raw.name,\n siteName: raw.siteName,\n description: raw.description,\n author: raw.author,\n url: raw.url,\n repository: raw.repository,\n install: raw.install,\n git: raw.git,\n yes: raw.yes,\n };\n return { provided } as const;\n};\n\nexport const runCli = async (\n args: readonly string[],\n version: string,\n context: CliContext = {},\n): Promise<CliResult> => {\n const parsed = parseArguments(args, version);\n if (!('provided' in parsed)) return parsed;\n\n try {\n const options = await resolveOptions(parsed.provided, {\n interactive: context.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY),\n prompts: context.prompts ?? clackPrompts,\n });\n return { output: '', exitCode: 0, options };\n } catch (error) {\n if (error instanceof CancellationError) return { output: `${error.message}\\n`, exitCode: 1 };\n if (error instanceof Error) return { output: `error: ${error.message}\\n`, exitCode: 1 };\n throw error;\n }\n};\n","const prefix = '__RM_INDUSTRIES_FORGE_TEMPLATE_V1_';\n\nexport const templateTokens = Object.freeze({\n packageName: `${prefix}PACKAGE_NAME__`,\n siteName: `${prefix}SITE_NAME__`,\n description: `${prefix}DESCRIPTION__`,\n author: `${prefix}AUTHOR__`,\n url: `${prefix}URL__`,\n repository: `${prefix}REPOSITORY__`,\n});\n\nexport const templateTokenPrefix = prefix;\n","import {\n chmod,\n copyFile,\n lstat,\n mkdir,\n mkdtemp,\n readdir,\n readFile,\n rm,\n rmdir,\n stat,\n writeFile,\n} from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { dirname, isAbsolute, join, parse, relative, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { GeneratorOptions } from './options';\nimport { templateTokenPrefix, templateTokens } from './template-tokens';\n\nexport class MaterializationError extends Error {}\n\ntype MaterializationContext = {\n templateDirectory: string | URL;\n cwd?: string;\n signal?: AbortSignal;\n confirmOverwrite?: (destination: string) => Promise<boolean>;\n onFileCopied?: (path: string) => void;\n};\n\ntype TemplateFile = { source: string; relativePath: string; mode: number };\n\nconst pathExists = async (path: string) => {\n try {\n await lstat(path);\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false;\n throw error;\n }\n};\n\nconst getPathStat = async (path: string) => {\n try {\n return await lstat(path);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n};\n\nconst assertNotAborted = (signal?: AbortSignal) => {\n if (signal?.aborted) throw new Error('Project creation was interrupted.');\n};\n\nconst resolveTemplateDirectory = (directory: string | URL) =>\n directory instanceof URL ? fileURLToPath(directory) : resolve(directory);\n\nconst resolveDestination = (destination: string, cwd: string) => {\n const segments = destination.split(/[\\\\/]/u);\n if (segments.includes('..')) {\n throw new MaterializationError(\n `Unsafe destination ${JSON.stringify(destination)}: parent traversal is not allowed.`,\n );\n }\n const resolved = isAbsolute(destination) ? resolve(destination) : resolve(cwd, destination);\n if (resolved === parse(resolved).root) {\n throw new MaterializationError(\n `Unsafe destination ${JSON.stringify(destination)}: a filesystem root is not allowed.`,\n );\n }\n return resolved;\n};\n\nconst listTemplateFiles = async (root: string, directory = root): Promise<TemplateFile[]> => {\n const entries = await readdir(directory, { withFileTypes: true });\n const files: TemplateFile[] = [];\n for (const entry of entries) {\n const source = join(directory, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await listTemplateFiles(root, source)));\n continue;\n }\n if (!entry.isFile()) throw new MaterializationError(`Template entry ${source} is not a regular file.`);\n const relativePath = relative(root, source);\n files.push({\n source,\n relativePath: relativePath === '.gitignore.template' ? '.gitignore' : relativePath,\n mode: (await stat(source)).mode,\n });\n }\n return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));\n};\n\nconst isDirectoryEmpty = async (directory: string) => (await readdir(directory)).length === 0;\n\nconst ensureDirectory = async (directory: string, created: Set<string>) => {\n const existing = await getPathStat(directory);\n if (existing) {\n if (existing.isSymbolicLink() || !existing.isDirectory()) {\n throw new Error(`Unsafe destination entry ${directory}: expected a regular directory.`);\n }\n return;\n }\n const parent = dirname(directory);\n if (parent !== directory) await ensureDirectory(parent, created);\n await mkdir(directory);\n created.add(directory);\n};\n\nconst quoteTypeScriptString = (value: string) =>\n `'${value\n .replaceAll('\\\\', '\\\\\\\\')\n .replaceAll(\"'\", \"\\\\'\")\n .replaceAll('\\u2028', '\\\\u2028')\n .replaceAll('\\u2029', '\\\\u2029')}'`;\n\nconst replaceToken = (source: string, token: string, value: string, field: string) => {\n const quotedToken = `'${token}'`;\n if (!source.includes(quotedToken)) throw new Error(`Packaged template is missing the ${field} token.`);\n return source.replaceAll(quotedToken, quoteTypeScriptString(value));\n};\n\nconst customizeTemplate = async (destination: string, options: GeneratorOptions) => {\n const packagePath = join(destination, 'package.json');\n const packageLockPath = join(destination, 'package-lock.json');\n const packageMetadata = JSON.parse(await readFile(packagePath, 'utf8')) as Record<string, unknown>;\n const packageLock = JSON.parse(await readFile(packageLockPath, 'utf8')) as {\n name?: string;\n packages?: Record<string, Record<string, unknown>>;\n };\n if (\n packageMetadata.name !== templateTokens.packageName ||\n packageLock.name !== templateTokens.packageName ||\n packageLock.packages?.['']?.name !== templateTokens.packageName\n ) {\n throw new Error('Packaged template contains unexpected package metadata tokens.');\n }\n packageMetadata.name = options.packageName;\n packageMetadata.version = '0.0.0';\n packageMetadata.private = true;\n packageLock.name = options.packageName;\n if (!packageLock.packages?.['']) throw new Error('Packaged template lockfile is missing its root package metadata.');\n packageLock.packages[''].name = options.packageName;\n packageLock.packages[''].version = '0.0.0';\n await writeFile(packagePath, `${JSON.stringify(packageMetadata, undefined, 2)}\\n`);\n await writeFile(packageLockPath, `${JSON.stringify(packageLock, undefined, 2)}\\n`);\n\n const siteConfigPath = join(destination, 'src', 'config', 'site.ts');\n let siteConfig = await readFile(siteConfigPath, 'utf8');\n siteConfig = replaceToken(siteConfig, templateTokens.siteName, options.siteName, 'site name');\n siteConfig = replaceToken(siteConfig, templateTokens.description, options.description, 'description');\n siteConfig = replaceToken(siteConfig, templateTokens.author, options.author, 'author');\n siteConfig = replaceToken(siteConfig, templateTokens.url, options.url, 'URL');\n siteConfig = replaceToken(siteConfig, templateTokens.repository, options.repository, 'repository');\n if (siteConfig.includes(templateTokenPrefix)) throw new Error('Packaged template contains unresolved tokens.');\n await writeFile(siteConfigPath, siteConfig);\n};\n\nexport const materializeProject = async (options: GeneratorOptions, context: MaterializationContext) => {\n const cwd = context.cwd ?? process.cwd();\n const destination = resolveDestination(options.destination, cwd);\n const templateDirectory = resolveTemplateDirectory(context.templateDirectory);\n const destinationExisted = await pathExists(destination);\n if (destinationExisted) {\n const destinationStat = await lstat(destination);\n if (destinationStat.isSymbolicLink() || !destinationStat.isDirectory()) {\n throw new MaterializationError(`Unsafe destination ${destination}: expected a regular directory.`);\n }\n }\n if (destinationExisted && !(await isDirectoryEmpty(destination))) {\n const confirmed = await context.confirmOverwrite?.(destination);\n if (!confirmed) {\n throw new MaterializationError(`Destination ${destination} is not empty. No files were changed.`);\n }\n }\n\n const files = await listTemplateFiles(templateDirectory);\n const backupDirectory = await mkdtemp(join(tmpdir(), 'create-forge-backup-'));\n const createdFiles = new Set<string>();\n const createdDirectories = new Set<string>();\n const backups = new Map<string, string>();\n\n try {\n assertNotAborted(context.signal);\n await ensureDirectory(destination, createdDirectories);\n for (const file of files) {\n assertNotAborted(context.signal);\n const target = join(destination, file.relativePath);\n const targetRelative = relative(destination, target);\n if (targetRelative.startsWith('..') || isAbsolute(targetRelative))\n throw new Error(`Unsafe template path ${file.relativePath}.`);\n await ensureDirectory(dirname(target), createdDirectories);\n const targetStat = await getPathStat(target);\n if (targetStat) {\n if (targetStat.isSymbolicLink() || !targetStat.isFile()) {\n throw new Error(`Unsafe destination entry ${target}: expected a regular file.`);\n }\n const backup = join(backupDirectory, file.relativePath);\n await mkdir(dirname(backup), { recursive: true });\n await copyFile(target, backup);\n backups.set(target, backup);\n } else {\n createdFiles.add(target);\n }\n await copyFile(file.source, target);\n await chmod(target, file.mode);\n context.onFileCopied?.(target);\n }\n assertNotAborted(context.signal);\n await customizeTemplate(destination, options);\n const tokenBytes = Buffer.from(templateTokenPrefix);\n for (const file of files) {\n assertNotAborted(context.signal);\n if ((await readFile(join(destination, file.relativePath))).includes(tokenBytes)) {\n throw new Error(`Generated file ${file.relativePath} contains an unresolved template token.`);\n }\n }\n return { destination, filesCopied: files.length };\n } catch (error) {\n const cleanupErrors: unknown[] = [];\n for (const [target, backup] of backups) {\n try {\n await copyFile(backup, target);\n } catch (cleanupError) {\n cleanupErrors.push(cleanupError);\n }\n }\n for (const file of createdFiles) {\n try {\n await rm(file, { force: true });\n } catch (cleanupError) {\n cleanupErrors.push(cleanupError);\n }\n }\n for (const directory of [...createdDirectories].sort((left, right) => right.length - left.length)) {\n try {\n await rmdir(directory);\n } catch (cleanupError) {\n if (!['ENOENT', 'ENOTEMPTY'].includes((cleanupError as NodeJS.ErrnoException).code ?? '')) {\n cleanupErrors.push(cleanupError);\n }\n }\n }\n const reason = error instanceof Error ? error.message : String(error);\n const recovery = cleanupErrors.length\n ? `Recovery: review ${destination}; some changes could not be rolled back automatically.`\n : `Recovery: changes from this invocation were rolled back; resolve the error and retry.`;\n throw new MaterializationError(`${reason} ${recovery}`, { cause: error });\n } finally {\n await rm(backupDirectory, { recursive: true, force: true });\n }\n};\n","import { spawn } from 'node:child_process';\n\nimport type { GeneratorOptions } from './options';\n\nexport class ProcessStepError extends Error {\n readonly exitCode: number;\n\n constructor(message: string, exitCode = 1, options?: ErrorOptions) {\n super(message, options);\n this.exitCode = exitCode;\n }\n}\n\nexport type Command = {\n executable: string;\n arguments: readonly string[];\n cwd: string;\n signal?: AbortSignal;\n environment?: NodeJS.ProcessEnv;\n};\n\nexport type CommandExecutor = (command: Command) => Promise<void>;\n\nconst signalExitCode = (signal: NodeJS.Signals | null) => {\n if (signal === 'SIGINT') return 130;\n if (signal === 'SIGTERM') return 143;\n return 1;\n};\n\nexport const executeCommand: CommandExecutor = ({ executable, arguments: args, cwd, signal, environment }) =>\n new Promise((resolve, reject) => {\n const child = spawn(executable, args, {\n cwd,\n stdio: 'inherit',\n ...(signal ? { signal } : {}),\n ...(environment ? { env: environment } : {}),\n });\n\n child.once('error', (error) => {\n const interrupted = signal?.aborted;\n reject(\n new ProcessStepError(\n interrupted\n ? `${executable} was interrupted. The generated project remains at ${cwd}.`\n : `Could not start ${executable}: ${error.message}. The generated project remains at ${cwd}.`,\n interrupted ? 130 : 1,\n { cause: error },\n ),\n );\n });\n child.once('close', (code, processSignal) => {\n if (code === 0) {\n resolve();\n return;\n }\n const detail = processSignal ? `signal ${processSignal}` : `exit code ${code ?? 'unknown'}`;\n reject(\n new ProcessStepError(\n `${executable} failed with ${detail}. The generated project remains at ${cwd}; review the output above and retry the command there.`,\n processSignal ? signalExitCode(processSignal) : (code ?? 1),\n ),\n );\n });\n });\n\ntype ProjectSetupContext = {\n destination: string;\n signal?: AbortSignal;\n execute?: CommandExecutor;\n environment?: NodeJS.ProcessEnv;\n};\n\nexport const runProjectSetup = async (options: GeneratorOptions, context: ProjectSetupContext) => {\n const execute = context.execute ?? executeCommand;\n const command = (executable: string, args: readonly string[]) =>\n execute({\n executable,\n arguments: args,\n cwd: context.destination,\n ...(context.signal ? { signal: context.signal } : {}),\n ...(context.environment ? { environment: context.environment } : {}),\n });\n\n if (options.install) await command('npm', ['install']);\n if (options.git) await command('git', ['init', '--initial-branch=main']);\n};\n","import { isAbsolute, relative } from 'node:path';\n\nimport type { GeneratorOptions } from './options';\n\ntype CompletionContext = {\n destination: string;\n cwd?: string;\n color?: boolean;\n};\n\nconst ansi = {\n bold: (value: string) => `\\u001B[1m${value}\\u001B[22m`,\n green: (value: string) => `\\u001B[32m${value}\\u001B[39m`,\n};\n\nconst commandPath = (path: string) => {\n if (/^[\\w./@-]+$/u.test(path)) return path;\n return `\"${path.replaceAll('\"', '\\\\\"')}\"`;\n};\n\nconst displayDestination = (destination: string, cwd: string) => {\n const path = relative(cwd, destination);\n if (!path) return '.';\n return isAbsolute(path) ? destination : path;\n};\n\nexport const supportsColor = (environment: NodeJS.ProcessEnv = process.env, terminal = Boolean(process.stdout.isTTY)) =>\n terminal && environment.NO_COLOR === undefined;\n\nexport const formatCompletion = (options: GeneratorOptions, context: CompletionContext) => {\n const cwd = context.cwd ?? process.cwd();\n const color = context.color ?? supportsColor();\n const decorate = color ? ansi : { bold: (value: string) => value, green: (value: string) => value };\n const destination = displayDestination(context.destination, cwd);\n const commands = [\n ...(destination === '.' ? [] : [`cd ${commandPath(destination)}`]),\n ...(options.install ? [] : ['npm install']),\n 'npm run dev',\n ];\n\n return `${decorate.green('✓')} Created ${options.siteName} in ${destination}\\n\\n${decorate.bold('Setup')}\\n Dependencies: ${options.install ? 'installed' : 'skipped'}\\n Git repository: ${options.git ? 'initialized' : 'skipped'}\\n\\n${decorate.bold('Next steps')}\\n${commands.map((command) => ` ${command}`).join('\\n')}\\n`;\n};\n","#!/usr/bin/env node\n\nimport { readFile } from 'node:fs/promises';\n\nimport { confirm, isCancel } from '@clack/prompts';\n\nimport { runCli } from './cli';\nimport { materializeProject } from './materialize';\nimport { ProcessStepError, runProjectSetup } from './process';\nimport { formatCompletion } from './reporter';\n\ntype PackageMetadata = {\n version: string;\n};\n\nconst packageJson = new URL('../package.json', import.meta.url);\nconst metadata = JSON.parse(await readFile(packageJson, 'utf8')) as PackageMetadata;\nconst interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);\nconst result = await runCli(process.argv.slice(2), metadata.version, { interactive });\n\nif (result.options) {\n const controller = new AbortController();\n let interruptedExitCode: number | undefined;\n const interrupt = () => {\n interruptedExitCode = 130;\n controller.abort();\n };\n const terminate = () => {\n interruptedExitCode = 143;\n controller.abort();\n };\n process.once('SIGINT', interrupt);\n process.once('SIGTERM', terminate);\n try {\n const materialized = await materializeProject(result.options, {\n templateDirectory: new URL('./template/', import.meta.url),\n signal: controller.signal,\n ...(interactive\n ? {\n confirmOverwrite: async (destination: string) => {\n const answer = await confirm({\n message: `${destination} is not empty. Continue and overwrite conflicting template files?`,\n initialValue: false,\n });\n return !isCancel(answer) && answer;\n },\n }\n : {}),\n });\n await runProjectSetup(result.options, {\n destination: materialized.destination,\n signal: controller.signal,\n });\n result.output = formatCompletion(result.options, { destination: materialized.destination });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n result.output = `error: ${message}\\n`;\n result.exitCode = interruptedExitCode ?? (error instanceof ProcessStepError ? error.exitCode : 1);\n } finally {\n process.off('SIGINT', interrupt);\n process.off('SIGTERM', terminate);\n }\n}\n\nprocess.stdout.write(result.output);\nprocess.exitCode = result.exitCode;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Created by https://www.toptal.com/developers/gitignore/api/vim,node,astro,linux,macos,windows,visualstudiocode
|
|
2
|
+
# Edit at https://www.toptal.com/developers/gitignore?templates=vim,node,astro,linux,macos,windows,visualstudiocode
|
|
3
|
+
|
|
4
|
+
### Astro ###
|
|
5
|
+
# Gitignore template for Astro projects
|
|
6
|
+
# Ignore content collection generated files
|
|
7
|
+
.astro
|
|
8
|
+
|
|
9
|
+
### Linux ###
|
|
10
|
+
*~
|
|
11
|
+
|
|
12
|
+
# temporary files which can be created if a process still has a handle open of a deleted file
|
|
13
|
+
.fuse_hidden*
|
|
14
|
+
|
|
15
|
+
# KDE directory preferences
|
|
16
|
+
.directory
|
|
17
|
+
|
|
18
|
+
# Linux trash folder which might appear on any partition or disk
|
|
19
|
+
.Trash-*
|
|
20
|
+
|
|
21
|
+
# .nfs files are created when an open file is removed but is still being accessed
|
|
22
|
+
.nfs*
|
|
23
|
+
|
|
24
|
+
### macOS ###
|
|
25
|
+
# General
|
|
26
|
+
.DS_Store
|
|
27
|
+
.AppleDouble
|
|
28
|
+
.LSOverride
|
|
29
|
+
|
|
30
|
+
# Icon must end with two \r
|
|
31
|
+
Icon
|
|
32
|
+
|
|
33
|
+
# Thumbnails
|
|
34
|
+
._*
|
|
35
|
+
|
|
36
|
+
# Files that might appear in the root of a volume
|
|
37
|
+
.DocumentRevisions-V100
|
|
38
|
+
.fseventsd
|
|
39
|
+
.Spotlight-V100
|
|
40
|
+
.TemporaryItems
|
|
41
|
+
.Trashes
|
|
42
|
+
.VolumeIcon.icns
|
|
43
|
+
.com.apple.timemachine.donotpresent
|
|
44
|
+
|
|
45
|
+
# Directories potentially created on remote AFP share
|
|
46
|
+
.AppleDB
|
|
47
|
+
.AppleDesktop
|
|
48
|
+
Network Trash Folder
|
|
49
|
+
Temporary Items
|
|
50
|
+
.apdisk
|
|
51
|
+
|
|
52
|
+
### macOS Patch ###
|
|
53
|
+
# iCloud generated files
|
|
54
|
+
*.icloud
|
|
55
|
+
|
|
56
|
+
### Node ###
|
|
57
|
+
# Logs
|
|
58
|
+
logs
|
|
59
|
+
*.log
|
|
60
|
+
npm-debug.log*
|
|
61
|
+
yarn-debug.log*
|
|
62
|
+
yarn-error.log*
|
|
63
|
+
lerna-debug.log*
|
|
64
|
+
.pnpm-debug.log*
|
|
65
|
+
|
|
66
|
+
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
67
|
+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
68
|
+
|
|
69
|
+
# Runtime data
|
|
70
|
+
pids
|
|
71
|
+
*.pid
|
|
72
|
+
*.seed
|
|
73
|
+
*.pid.lock
|
|
74
|
+
|
|
75
|
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
76
|
+
lib-cov
|
|
77
|
+
|
|
78
|
+
# Coverage directory used by tools like istanbul
|
|
79
|
+
coverage
|
|
80
|
+
*.lcov
|
|
81
|
+
|
|
82
|
+
# nyc test coverage
|
|
83
|
+
.nyc_output
|
|
84
|
+
|
|
85
|
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
86
|
+
.grunt
|
|
87
|
+
|
|
88
|
+
# Bower dependency directory (https://bower.io/)
|
|
89
|
+
bower_components
|
|
90
|
+
|
|
91
|
+
# node-waf configuration
|
|
92
|
+
.lock-wscript
|
|
93
|
+
|
|
94
|
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
95
|
+
build/Release
|
|
96
|
+
|
|
97
|
+
# Dependency directories
|
|
98
|
+
node_modules/
|
|
99
|
+
jspm_packages/
|
|
100
|
+
|
|
101
|
+
# Snowpack dependency directory (https://snowpack.dev/)
|
|
102
|
+
web_modules/
|
|
103
|
+
|
|
104
|
+
# TypeScript cache
|
|
105
|
+
*.tsbuildinfo
|
|
106
|
+
|
|
107
|
+
# Optional npm cache directory
|
|
108
|
+
.npm
|
|
109
|
+
|
|
110
|
+
# Optional eslint cache
|
|
111
|
+
.eslintcache
|
|
112
|
+
|
|
113
|
+
# Optional stylelint cache
|
|
114
|
+
.stylelintcache
|
|
115
|
+
|
|
116
|
+
# Microbundle cache
|
|
117
|
+
.rpt2_cache/
|
|
118
|
+
.rts2_cache_cjs/
|
|
119
|
+
.rts2_cache_es/
|
|
120
|
+
.rts2_cache_umd/
|
|
121
|
+
|
|
122
|
+
# Optional REPL history
|
|
123
|
+
.node_repl_history
|
|
124
|
+
|
|
125
|
+
# Output of 'npm pack'
|
|
126
|
+
*.tgz
|
|
127
|
+
|
|
128
|
+
# Yarn Integrity file
|
|
129
|
+
.yarn-integrity
|
|
130
|
+
|
|
131
|
+
# dotenv environment variable files
|
|
132
|
+
.env
|
|
133
|
+
.env.development.local
|
|
134
|
+
.env.test.local
|
|
135
|
+
.env.production.local
|
|
136
|
+
.env.local
|
|
137
|
+
|
|
138
|
+
# parcel-bundler cache (https://parceljs.org/)
|
|
139
|
+
.cache
|
|
140
|
+
.parcel-cache
|
|
141
|
+
|
|
142
|
+
# Next.js build output
|
|
143
|
+
.next
|
|
144
|
+
out
|
|
145
|
+
|
|
146
|
+
# Nuxt.js build / generate output
|
|
147
|
+
.nuxt
|
|
148
|
+
dist
|
|
149
|
+
|
|
150
|
+
# Gatsby files
|
|
151
|
+
.cache/
|
|
152
|
+
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
153
|
+
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
154
|
+
# public
|
|
155
|
+
|
|
156
|
+
# vuepress build output
|
|
157
|
+
.vuepress/dist
|
|
158
|
+
|
|
159
|
+
# vuepress v2.x temp and cache directory
|
|
160
|
+
.temp
|
|
161
|
+
|
|
162
|
+
# Docusaurus cache and generated files
|
|
163
|
+
.docusaurus
|
|
164
|
+
|
|
165
|
+
# Serverless directories
|
|
166
|
+
.serverless/
|
|
167
|
+
|
|
168
|
+
# FuseBox cache
|
|
169
|
+
.fusebox/
|
|
170
|
+
|
|
171
|
+
# DynamoDB Local files
|
|
172
|
+
.dynamodb/
|
|
173
|
+
|
|
174
|
+
# TernJS port file
|
|
175
|
+
.tern-port
|
|
176
|
+
|
|
177
|
+
# Stores VSCode versions used for testing VSCode extensions
|
|
178
|
+
.vscode-test
|
|
179
|
+
|
|
180
|
+
# yarn v2
|
|
181
|
+
.yarn/cache
|
|
182
|
+
.yarn/unplugged
|
|
183
|
+
.yarn/build-state.yml
|
|
184
|
+
.yarn/install-state.gz
|
|
185
|
+
.pnp.*
|
|
186
|
+
|
|
187
|
+
### Node Patch ###
|
|
188
|
+
# Serverless Webpack directories
|
|
189
|
+
.webpack/
|
|
190
|
+
|
|
191
|
+
# Optional stylelint cache
|
|
192
|
+
|
|
193
|
+
# SvelteKit build / generate output
|
|
194
|
+
.svelte-kit
|
|
195
|
+
|
|
196
|
+
### Vim ###
|
|
197
|
+
# Swap
|
|
198
|
+
[._]*.s[a-v][a-z]
|
|
199
|
+
!*.svg # comment out if you don't need vector files
|
|
200
|
+
[._]*.sw[a-p]
|
|
201
|
+
[._]s[a-rt-v][a-z]
|
|
202
|
+
[._]ss[a-gi-z]
|
|
203
|
+
[._]sw[a-p]
|
|
204
|
+
|
|
205
|
+
# Session
|
|
206
|
+
Session.vim
|
|
207
|
+
Sessionx.vim
|
|
208
|
+
|
|
209
|
+
# Temporary
|
|
210
|
+
.netrwhist
|
|
211
|
+
# Auto-generated tag files
|
|
212
|
+
tags
|
|
213
|
+
# Persistent undo
|
|
214
|
+
[._]*.un~
|
|
215
|
+
|
|
216
|
+
### VisualStudioCode ###
|
|
217
|
+
.vscode/*
|
|
218
|
+
!.vscode/settings.json
|
|
219
|
+
!.vscode/tasks.json
|
|
220
|
+
!.vscode/launch.json
|
|
221
|
+
!.vscode/extensions.json
|
|
222
|
+
!.vscode/*.code-snippets
|
|
223
|
+
|
|
224
|
+
# Local History for Visual Studio Code
|
|
225
|
+
.history/
|
|
226
|
+
|
|
227
|
+
# Built Visual Studio Code Extensions
|
|
228
|
+
*.vsix
|
|
229
|
+
|
|
230
|
+
### VisualStudioCode Patch ###
|
|
231
|
+
# Ignore all local history of files
|
|
232
|
+
.history
|
|
233
|
+
.ionide
|
|
234
|
+
|
|
235
|
+
### Windows ###
|
|
236
|
+
# Windows thumbnail cache files
|
|
237
|
+
Thumbs.db
|
|
238
|
+
Thumbs.db:encryptable
|
|
239
|
+
ehthumbs.db
|
|
240
|
+
ehthumbs_vista.db
|
|
241
|
+
|
|
242
|
+
# Dump file
|
|
243
|
+
*.stackdump
|
|
244
|
+
|
|
245
|
+
# Folder config file
|
|
246
|
+
[Dd]esktop.ini
|
|
247
|
+
|
|
248
|
+
# Recycle Bin used on file shares
|
|
249
|
+
$RECYCLE.BIN/
|
|
250
|
+
|
|
251
|
+
# Windows Installer files
|
|
252
|
+
*.cab
|
|
253
|
+
*.msi
|
|
254
|
+
*.msix
|
|
255
|
+
*.msm
|
|
256
|
+
*.msp
|
|
257
|
+
|
|
258
|
+
# Windows shortcuts
|
|
259
|
+
*.lnk
|
|
260
|
+
|
|
261
|
+
# End of https://www.toptal.com/developers/gitignore/api/vim,node,astro,linux,macos,windows,visualstudiocode
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ci": {
|
|
3
|
+
"collect": {
|
|
4
|
+
"staticDistDir": "./dist",
|
|
5
|
+
"url": [
|
|
6
|
+
"http://localhost/",
|
|
7
|
+
"http://localhost/articles/",
|
|
8
|
+
"http://localhost/articles/designing-a-calm-starting-point/"
|
|
9
|
+
],
|
|
10
|
+
"numberOfRuns": 3
|
|
11
|
+
},
|
|
12
|
+
"assert": {
|
|
13
|
+
"aggregationMethod": "median",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"categories:performance": ["error", { "minScore": 0.9 }],
|
|
16
|
+
"categories:accessibility": ["error", { "minScore": 1 }],
|
|
17
|
+
"categories:best-practices": ["error", { "minScore": 0.95 }],
|
|
18
|
+
"categories:seo": ["error", { "minScore": 0.95 }],
|
|
19
|
+
"resource-summary:script:size": ["error", { "maxNumericValue": 0 }],
|
|
20
|
+
"resource-summary:total:size": ["error", { "maxNumericValue": 125000 }]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"upload": {
|
|
24
|
+
"target": "filesystem",
|
|
25
|
+
"outputDir": ".lighthouseci"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/template/README.md
CHANGED
|
@@ -11,30 +11,87 @@ The template's `.editorconfig` shares UTF-8, LF, final-newline, two-space, and
|
|
|
11
11
|
120-column settings with supported editors and Oxfmt. `oxfmt.config.ts` contains
|
|
12
12
|
only formatter-specific behavior such as quote and import ordering.
|
|
13
13
|
|
|
14
|
-
| Command
|
|
15
|
-
|
|
|
16
|
-
| `npm run dev`
|
|
17
|
-
| `npm run build`
|
|
18
|
-
| `npm run
|
|
19
|
-
| `npm run
|
|
20
|
-
| `npm run
|
|
21
|
-
| `npm run
|
|
22
|
-
| `npm run format
|
|
23
|
-
| `npm run
|
|
24
|
-
| `npm run lint
|
|
25
|
-
| `npm run lint:
|
|
26
|
-
| `npm run lint:
|
|
27
|
-
| `npm run
|
|
28
|
-
| `npm
|
|
29
|
-
| `npm run
|
|
30
|
-
| `npm run
|
|
31
|
-
| `npm
|
|
14
|
+
| Command | Purpose |
|
|
15
|
+
| ------------------------ | ---------------------------------------------------------------- |
|
|
16
|
+
| `npm run dev` | Start Astro's local development server. |
|
|
17
|
+
| `npm run build` | Create the production site in `dist/`. |
|
|
18
|
+
| `npm run validate:build` | Validate required output and scan generated text. |
|
|
19
|
+
| `npm run preview` | Serve the production build locally. Run `build` first. |
|
|
20
|
+
| `npm run typecheck` | Generate Astro types and run TypeScript without emitting files. |
|
|
21
|
+
| `npm run astro:check` | Validate Astro components, content, and diagnostics. |
|
|
22
|
+
| `npm run format` | Check formatting with Oxfmt. |
|
|
23
|
+
| `npm run format:fix` | Apply Oxfmt formatting. |
|
|
24
|
+
| `npm run lint` | Run code, CSS, Markdown, and spelling checks. |
|
|
25
|
+
| `npm run lint:code` | Check JavaScript and TypeScript with Oxlint. |
|
|
26
|
+
| `npm run lint:css` | Check CSS with Stylelint. |
|
|
27
|
+
| `npm run lint:markdown` | Check Markdown with Markdownlint. |
|
|
28
|
+
| `npm run spellcheck` | Check repository text with cspell. |
|
|
29
|
+
| `npm run audit:unused` | Detect unused files, exports, and dependencies with Knip. |
|
|
30
|
+
| `npm run lighthouse:ci` | Build and enforce Lighthouse scores and transfer budgets. |
|
|
31
|
+
| `npm test` | Run the `test:unit` command. |
|
|
32
|
+
| `npm run test:unit` | Run deterministic TypeScript unit tests with Vitest. |
|
|
33
|
+
| `npm run test:a11y` | Run focused Playwright and axe accessibility checks. |
|
|
34
|
+
| `npm run test:coverage` | Run unit tests and enforce V8 coverage thresholds. |
|
|
35
|
+
| `npm run test:e2e` | Run browser and accessibility tests with Playwright. |
|
|
36
|
+
| `npm run audit` | Reject unapproved high-severity dependency vulnerabilities. |
|
|
37
|
+
| `npm run quality:static` | Run every static-quality and dependency audit. |
|
|
38
|
+
| `npm run quality:core` | Run static checks, coverage tests, build, and output validation. |
|
|
39
|
+
| `npm run quality` | Run the complete generated-project quality pipeline. |
|
|
32
40
|
|
|
33
41
|
During development, run `npm run dev`. Before committing, run
|
|
34
|
-
`npm run quality
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
`npm run quality`. It runs static validation and dependency auditing, unit tests
|
|
43
|
+
with coverage thresholds, a production build and generated-output validation,
|
|
44
|
+
the complete Playwright and axe suite, and Lighthouse budgets in that order.
|
|
45
|
+
Each stage is joined with `&&`, so the command stops immediately and returns a
|
|
46
|
+
non-zero status when any stage fails. Browser tests and Lighthouse start from
|
|
47
|
+
their own production build so they remain independently runnable and cannot
|
|
48
|
+
accidentally inspect stale output.
|
|
49
|
+
|
|
50
|
+
The complete command is intentionally high cost. Use `npm run quality:static`
|
|
51
|
+
for the quickest source-only feedback or `npm run quality:core` for every check
|
|
52
|
+
that does not launch a browser. Both commands include `npm run audit`, so they
|
|
53
|
+
query the npm registry and can change when new advisories are published. Run
|
|
54
|
+
the individual scripts when working offline. CI may execute independent stages
|
|
55
|
+
in parallel, but a project generated by the packed Forge initializer must pass
|
|
56
|
+
the documented `npm run quality` command as a single ordered pipeline.
|
|
57
|
+
|
|
58
|
+
Playwright builds the site and runs Chromium against Astro's production preview,
|
|
59
|
+
not the development server. Its browser coverage exercises desktop and mobile
|
|
60
|
+
navigation, article listing/detail flows, article pagination, light and dark
|
|
61
|
+
themes, keyboard use, missing-page recovery, metadata, supporting site files,
|
|
62
|
+
and content-manager startup. Each test receives a fresh browser context, and
|
|
63
|
+
tests use accessible roles and names where the rendered interface provides
|
|
64
|
+
them. On failure, Playwright retains screenshots and traces in `test-results/`;
|
|
65
|
+
the HTML report is written to `playwright-report/`. CI retains both directories
|
|
66
|
+
for seven days, including failed runs.
|
|
67
|
+
|
|
68
|
+
Accessibility verification combines automation with the dated manual review in
|
|
69
|
+
`docs/accessibility-checklist.md`. Repeat that checklist after changing content,
|
|
70
|
+
navigation, themes, components, or interactive behavior; automated axe checks
|
|
71
|
+
cannot judge every aspect of reading order, language, focus order, contrast, or
|
|
72
|
+
alternative-text quality.
|
|
73
|
+
|
|
74
|
+
Lighthouse CI runs three times each against the production home, article
|
|
75
|
+
listing, and article detail pages, then enforces median category scores and
|
|
76
|
+
transfer budgets. The measured baseline, headroom, exclusions, and update rules
|
|
77
|
+
are documented in `docs/performance-budget.md`. Reports are written to the
|
|
78
|
+
generated `.lighthouseci/` directory; CI retains them for seven days even when
|
|
79
|
+
an assertion fails.
|
|
80
|
+
|
|
81
|
+
The configured exclusions cover only generated output, dependency directories,
|
|
82
|
+
and tool artifacts. Stylelint's exceptions recognize Tailwind and DaisyUI
|
|
83
|
+
directives used by `src/styles/global.css`; they do not suppress ordinary CSS
|
|
84
|
+
rules. Knip uses its Astro integration without an ignore list, and Oxlint uses
|
|
85
|
+
its recommended defaults without project-specific rule suppression.
|
|
86
|
+
|
|
87
|
+
Vitest coverage includes configuration, shared content-model registration,
|
|
88
|
+
Astro and Sveltia integration code, content utilities, URL helpers, theme
|
|
89
|
+
configuration, and preview registration. Statements, branches, functions, and
|
|
90
|
+
lines must remain at 100%. Add direct positive and negative tests when this
|
|
91
|
+
source grows; do not lower thresholds or exclude source merely to make a change
|
|
92
|
+
pass. HTML details are written to `coverage/`, which is generated and ignored.
|
|
93
|
+
CI enforces the same thresholds and retains the HTML report for seven days,
|
|
94
|
+
including when the coverage job fails.
|
|
38
95
|
|
|
39
96
|
Edit `src/config/site.ts` to change the site name, description, author,
|
|
40
97
|
canonical URL, repository, language, navigation, social links, and derived CMS branding. The Astro
|