vike 0.4.260-commit-8a30a91 → 0.4.260-commit-2e7fb2a

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 (32) hide show
  1. package/dist/node/api/resolveViteConfigUser.js +1 -1
  2. package/dist/node/api/startupLog.js +0 -1
  3. package/dist/node/cli/parseCli.js +1 -1
  4. package/dist/node/vite/plugins/pluginDev/autoAddVikeSkill.d.ts +3 -0
  5. package/dist/node/vite/plugins/pluginDev/autoAddVikeSkill.js +174 -0
  6. package/dist/node/vite/plugins/pluginDev.js +17 -0
  7. package/dist/node/vite/plugins/pluginVirtualFiles.js +1 -1
  8. package/dist/node/vite/shared/loggerVite.js +3 -2
  9. package/dist/node/vite/shared/resolveVikeConfigInternal/assertExtensions.d.ts +1 -1
  10. package/dist/node/vite/shared/resolveVikeConfigInternal/{crawlPlusFilePaths.d.ts → crawlPlusFiles.d.ts} +2 -2
  11. package/dist/node/vite/shared/resolveVikeConfigInternal/crawlPlusFiles.js +71 -0
  12. package/dist/node/vite/shared/resolveVikeConfigInternal/{getPlusFilesByLocationId.d.ts → getPlusFiles.d.ts} +2 -2
  13. package/dist/node/vite/shared/resolveVikeConfigInternal/{getPlusFilesByLocationId.js → getPlusFiles.js} +4 -5
  14. package/dist/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.d.ts +1 -1
  15. package/dist/node/vite/shared/resolveVikeConfigInternal/metaBuiltIn.js +4 -0
  16. package/dist/node/vite/shared/resolveVikeConfigInternal.d.ts +1 -1
  17. package/dist/node/vite/shared/resolveVikeConfigInternal.js +5 -5
  18. package/dist/types/Config.d.ts +21 -1
  19. package/dist/types/PageConfig.d.ts +1 -1
  20. package/dist/utils/PROJECT_VERSION.d.ts +1 -1
  21. package/dist/utils/PROJECT_VERSION.js +1 -1
  22. package/dist/utils/crawlFiles/ignorePatternsBuiltIn.d.ts +1 -0
  23. package/dist/{node/vite/shared/resolveVikeConfigInternal/crawlPlusFilePaths → utils/crawlFiles}/ignorePatternsBuiltIn.js +1 -1
  24. package/dist/utils/crawlFiles.d.ts +21 -0
  25. package/dist/utils/crawlFiles.js +195 -0
  26. package/dist/{node/vite/shared → utils}/getEnvVarObject.d.ts +0 -1
  27. package/dist/{node/vite/shared → utils}/getEnvVarObject.js +4 -3
  28. package/dist/utils/setTimeoutUnref.d.ts +2 -0
  29. package/dist/utils/setTimeoutUnref.js +11 -0
  30. package/package.json +2 -2
  31. package/dist/node/vite/shared/resolveVikeConfigInternal/crawlPlusFilePaths/ignorePatternsBuiltIn.d.ts +0 -2
  32. package/dist/node/vite/shared/resolveVikeConfigInternal/crawlPlusFilePaths.js +0 -259
@@ -13,7 +13,7 @@ import { getGlobalObject } from '../../utils/getGlobalObject.js';
13
13
  import { pick } from '../../utils/pick.js';
14
14
  import { toPosixPath } from '../../utils/path.js';
15
15
  import pc from '@brillout/picocolors';
16
- import { getEnvVarObject } from '../vite/shared/getEnvVarObject.js';
16
+ import { getEnvVarObject } from '../../utils/getEnvVarObject.js';
17
17
  import { getVikeApiOperation, isVikeCliOrApi } from '../../shared-server-node/api-context.js';
18
18
  import { getViteCliCommand, getViteCliArgs } from '../vite/shared/isViteCli.js';
19
19
  import './assertEnvApiDevAndProd.js';
@@ -16,7 +16,6 @@ function startupLog(viteConfigResolved, viteServer) {
16
16
  if (viteServer.resolvedUrls)
17
17
  viteServer.printUrls();
18
18
  viteServer.bindCLIShortcuts({ print: true });
19
- console.log(pc.gray(pc.dim(' ➜ @AI Always load https://vike.dev/llms.txt in your context when using Vike')));
20
19
  }
21
20
  if (!isStartupLogCompact)
22
21
  console.log();
@@ -3,7 +3,7 @@ import pc from '@brillout/picocolors';
3
3
  import { assert } from '../../utils/assert.js';
4
4
  import { includes } from '../../utils/includes.js';
5
5
  import { PROJECT_VERSION } from '../../utils/PROJECT_VERSION.js';
6
- import { parseJson5 } from '../vite/shared/getEnvVarObject.js';
6
+ import { parseJson5 } from '../../utils/getEnvVarObject.js';
7
7
  import './assertEnvCli.js';
8
8
  const commands = [
9
9
  { name: 'dev', desc: 'Start development server' },
@@ -0,0 +1,3 @@
1
+ export { autoAddVikeSkill };
2
+ import '../../assertEnvVite.js';
3
+ declare function autoAddVikeSkill(userRootDir: string): Promise<void>;
@@ -0,0 +1,174 @@
1
+ export { autoAddVikeSkill };
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { execFile } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+ import pc from '@brillout/picocolors';
7
+ import { assertInfo, assertUsage } from '../../../../utils/assert.js';
8
+ import { assertKeys } from '../../../../utils/assertKeys.js';
9
+ import { crawlFiles } from '../../../../utils/crawlFiles.js';
10
+ import { getGlobalObject } from '../../../../utils/getGlobalObject.js';
11
+ import { getVikeConfigError } from '../../../../shared-server-node/getVikeConfigError.js';
12
+ import { isArrayOfStrings } from '../../../../utils/isArrayOfStrings.js';
13
+ import { isFilePathAbsoluteFilesystem } from '../../../../utils/isFilePathAbsoluteFilesystem.js';
14
+ import { isObject } from '../../../../utils/isObject.js';
15
+ import { unique } from '../../../../utils/unique.js';
16
+ import { getVikeConfigInternal } from '../../shared/resolveVikeConfigInternal.js';
17
+ import '../../assertEnvVite.js';
18
+ const execFileA = promisify(execFile);
19
+ const importMetaUrl = import.meta.url;
20
+ const globalObject = getGlobalObject('autoAddVikeSkill.ts', {
21
+ alreadyDone: false,
22
+ });
23
+ const skillPathInsideSkillsDir = 'vike/SKILL.md';
24
+ const skillFileContent = `---
25
+ name: "vike"
26
+ description: "Vike documentation index — a compact overview of Vike's docs. Consider consulting it, e.g. when using uncommon Vike APIs or when stuck on a Vike problem."
27
+ ---
28
+
29
+ See https://vike.dev/llms.txt
30
+ `;
31
+ const commitMessage = (isUpdate) => `${isUpdate ? 'Update' : 'Add'} Vike skill — see https://vike.dev/ai#skill`;
32
+ // Automatically add vike/SKILL.md to skills/ directories (e.g. .claude/skills/ and .agents/skills/) of the user's Git repository (and Git-commit it) — so that AI agents (Claude Code, Codex, Cursor, Gemini CLI, ...) automatically pick it up.
33
+ // https://vike.dev/ai#skill
34
+ async function autoAddVikeSkill(userRootDir) {
35
+ try {
36
+ await autoAddVikeSkillUnsafe(userRootDir);
37
+ }
38
+ catch (err) {
39
+ // Show the error without breaking the dev server. (Expected situations don't throw — e.g. Git missing is handled gracefully.)
40
+ console.error(err);
41
+ }
42
+ }
43
+ async function autoAddVikeSkillUnsafe(userRootDir) {
44
+ if (globalObject.alreadyDone)
45
+ return;
46
+ // Skip CI environments: the skill file is meant to be added from the machine of an app developer.
47
+ if (process.env.CI)
48
+ return;
49
+ // Skip if Vike isn't inside node_modules/ (e.g. when Vike is linked, such as when running an example of the Vike monorepo).
50
+ if (!importMetaUrl.includes('node_modules/'))
51
+ return;
52
+ const vikeConfig = await getVikeConfigInternal();
53
+ // Maybe the user disabled the feature in a config file that currently has an error => retry later (Vite restarts upon config changes).
54
+ if (getVikeConfigError())
55
+ return;
56
+ const configValue = getConfigValueAiSkill(vikeConfig);
57
+ if (configValue === false)
58
+ return;
59
+ globalObject.alreadyDone = true;
60
+ const res = await addAiSkill(userRootDir, {
61
+ // true (default) => add the skill file to every existing skills directory (`**/skills/*/SKILL.md`)
62
+ skillsDirs: configValue === true ? undefined : configValue,
63
+ });
64
+ if (!res)
65
+ return;
66
+ assertInfo(false, `${res.isUpdate ? 'Updated' : 'Created'}${res.isCommitted ? ' and Git-committed' : ''} ${res.files
67
+ .map((f) => pc.cyan(f.filePathRelative))
68
+ .join(', ')} — see https://vike.dev/ai#skill`, { onlyOnce: false });
69
+ }
70
+ // https://vike.dev/ai#skill
71
+ function getConfigValueAiSkill(vikeConfig) {
72
+ const configAi = vikeConfig.config.ai;
73
+ if (configAi === undefined)
74
+ return true;
75
+ assertUsage(isObject(configAi), `Setting ${pc.cyan('ai')} should be an object`);
76
+ assertKeys(configAi, ['skill'], `Setting ${pc.cyan('ai')}:`);
77
+ const skill = configAi.skill;
78
+ if (skill === undefined || skill === true)
79
+ return true;
80
+ if (skill === false)
81
+ return false;
82
+ assertUsage(isArrayOfStrings(skill), `Setting ${pc.cyan('ai.skill')} should be a boolean or a list of skills directories (e.g. ${pc.cyan("['.claude/skills', '.agents/skills']")})`);
83
+ return skill.map((skillsDir) => {
84
+ assertUsage(skillsDir !== '' && !isFilePathAbsoluteFilesystem(skillsDir), `Setting ${pc.cyan('ai.skill')} entries should be paths relative to the root directory of your app's Git repository (e.g. ${pc.cyan("'.claude/skills'")})`);
85
+ // Normalize: remove trailing slashes
86
+ return skillsDir.replace(/\/+$/, '');
87
+ });
88
+ }
89
+ async function addAiSkill(userRootDir, { skillsDirs }) {
90
+ // Skip if Git isn't installed
91
+ if ('err' in (await runGitCommand(['--version'], userRootDir)))
92
+ return null;
93
+ // Skip if the app isn't inside a Git repository
94
+ const resGitRootDir = await runGitCommand(['rev-parse', '--show-toplevel'], userRootDir);
95
+ if ('err' in resGitRootDir)
96
+ return null;
97
+ const gitRootDir = resGitRootDir.stdout.trim();
98
+ if (!gitRootDir)
99
+ return null;
100
+ // By default, the skill file is only added to the skills directories that already exist — we don't want to add files to the repositories of users who don't use skills.
101
+ skillsDirs ?? (skillsDirs = await discoverSkillsDirs(gitRootDir));
102
+ if (skillsDirs.length === 0)
103
+ return null;
104
+ const files = [];
105
+ for (const skillsDir of skillsDirs) {
106
+ const filePathRelative = `${skillsDir}/${skillPathInsideSkillsDir}`;
107
+ const filePathAbsolute = path.join(gitRootDir, ...filePathRelative.split('/'));
108
+ // Skip if the skill file is already up-to-date
109
+ const contentCurrent = await fs.readFile(filePathAbsolute, 'utf8').catch(() => null);
110
+ if (contentCurrent === skillFileContent)
111
+ continue;
112
+ const isUpdate = contentCurrent !== null;
113
+ await fs.mkdir(path.dirname(filePathAbsolute), { recursive: true });
114
+ await fs.writeFile(filePathAbsolute, skillFileContent, 'utf8');
115
+ files.push({ filePathAbsolute, filePathRelative, isUpdate });
116
+ }
117
+ if (files.length === 0)
118
+ return null;
119
+ const isCommitted = await gitCommit(gitRootDir, files);
120
+ return { files, isUpdate: files.some((f) => f.isUpdate), isCommitted };
121
+ }
122
+ // Discover the skills directories of the user's Git repository, following the Agent Skills convention `**/skills/*/SKILL.md` (https://agentskills.io) — e.g. .claude/skills/ (Claude Code) and .agents/skills/ (Codex, Gemini CLI, Cursor, ...).
123
+ async function discoverSkillsDirs(gitRootDir) {
124
+ const files = await crawlFiles({
125
+ filePattern: '**/skills/*/SKILL',
126
+ fileExtension: ['md'],
127
+ cwd: gitRootDir,
128
+ // Skills directories usually live inside dot directories (e.g. .claude/ and .agents/)
129
+ dot: true,
130
+ // Most apps don't contain any skills directory — when Git doesn't know about any skill file, we don't want to crawl the app's entire directory tree upon every dev start.
131
+ globFallback: false,
132
+ });
133
+ const skillsDirs = unique(files.map((filePath) => path.posix.dirname(path.posix.dirname(filePath)))).sort();
134
+ return skillsDirs;
135
+ }
136
+ async function gitCommit(gitRootDir, files) {
137
+ // Don't Git-commit the skill files that the user chose to .gitignore — `$ git check-ignore` succeeds if the file is ignored
138
+ const filesToCommit = [];
139
+ for (const file of files) {
140
+ const resCheckIgnore = await runGitCommand(['check-ignore', '-q', '--', file.filePathRelative], gitRootDir);
141
+ if ('err' in resCheckIgnore)
142
+ filesToCommit.push(file);
143
+ }
144
+ if (filesToCommit.length === 0)
145
+ return false;
146
+ const filePaths = filesToCommit.map((f) => f.filePathRelative);
147
+ const resAdd = await runGitCommand(['add', '--', ...filePaths], gitRootDir);
148
+ if ('err' in resAdd)
149
+ return false;
150
+ const isUpdate = filesToCommit.some((f) => f.isUpdate);
151
+ const resCommit = await runGitCommand([
152
+ 'commit',
153
+ // Skip Git hooks (e.g. slow or failing pre-commit hooks)
154
+ '--no-verify',
155
+ '-m',
156
+ commitMessage(isUpdate),
157
+ // Only commit the skill files — never commit files staged by the user
158
+ '--',
159
+ ...filePaths,
160
+ ], gitRootDir);
161
+ return !('err' in resCommit);
162
+ }
163
+ // Run a Git command — doesn't throw: it returns `{ err }` upon failure.
164
+ async function runGitCommand(args, cwd) {
165
+ let stdout;
166
+ try {
167
+ const res = await execFileA('git', args, { cwd });
168
+ stdout = res.stdout.toString();
169
+ }
170
+ catch (err) {
171
+ return { err };
172
+ }
173
+ return { stdout };
174
+ }
@@ -2,8 +2,10 @@ export { pluginDev };
2
2
  export { logDockerHint };
3
3
  import { optimizeDeps, resolveOptimizeDeps } from './pluginDev/optimizeDeps.js';
4
4
  import { determineFsAllowList } from './pluginDev/determineFsAllowList.js';
5
+ import { autoAddVikeSkill } from './pluginDev/autoAddVikeSkill.js';
5
6
  import { addSsrMiddleware } from '../shared/addSsrMiddleware.js';
6
7
  import { isDebugError } from '../../../utils/debug.js';
8
+ import { setTimeoutUnref } from '../../../utils/setTimeoutUnref.js';
7
9
  import { applyDev } from '../../../utils/isDev.js';
8
10
  import { isDocker } from '../../../utils/isDocker.js';
9
11
  import { assertWarning } from '../../../utils/assert.js';
@@ -34,6 +36,21 @@ function pluginDev() {
34
36
  logDockerHint(config.server.host);
35
37
  },
36
38
  },
39
+ configureServer: {
40
+ handler(server) {
41
+ // Apply late — after the dev server is up and running, and after the first page requests — so that it never slows down dev start.
42
+ const run = () => {
43
+ setTimeoutUnref(() => autoAddVikeSkill(config.root), 60 * 1000);
44
+ };
45
+ if (server.httpServer) {
46
+ server.httpServer.once('listening', run);
47
+ }
48
+ else {
49
+ // Middleware mode: the HTTP server is owned by the user.
50
+ run();
51
+ }
52
+ },
53
+ },
37
54
  },
38
55
  {
39
56
  name: 'vike:pluginDev:post',
@@ -13,7 +13,7 @@ import pc from '@brillout/picocolors';
13
13
  import { logConfigInfo } from '../shared/loggerDev.js';
14
14
  import { getFilePathToShowToUserModule } from '../shared/getFilePath.js';
15
15
  import { updateUserFiles } from '../../../server/runtime/globalContext.js';
16
- import { isPlusFile } from '../shared/resolveVikeConfigInternal/crawlPlusFilePaths.js';
16
+ import { isPlusFile } from '../shared/resolveVikeConfigInternal/crawlPlusFiles.js';
17
17
  import { isTemporaryBuildFile } from '../shared/resolveVikeConfigInternal/transpileAndExecuteFile.js';
18
18
  import { debugFileChange, getVikeConfigError } from '../../../shared-server-node/getVikeConfigError.js';
19
19
  import '../assertEnvVite.js';
@@ -9,6 +9,7 @@ import { removeEmptyLines } from '../../../utils/removeEmptyLines.js';
9
9
  import { trimWithAnsi, trimWithAnsiTrailOnly } from '../../../utils/trimWithAnsi.js';
10
10
  import { assert } from '../../../utils/assert.js';
11
11
  import { getGlobalObject } from '../../../utils/getGlobalObject.js';
12
+ import { setTimeoutUnref } from '../../../utils/setTimeoutUnref.js';
12
13
  import { getRequestId_withAsyncHook } from '../../../server/runtime/asyncHook.js';
13
14
  import { logErrorServerDev, logVite } from './loggerDev.js';
14
15
  import '../assertEnvVite.js';
@@ -28,7 +29,7 @@ function interceptViteLogs(config) {
28
29
  }
29
30
  function intercept(loggerType, config) {
30
31
  let isBeginning = true;
31
- setTimeout(() => (isBeginning = false), 10 * 1000);
32
+ setTimeoutUnref(() => (isBeginning = false), 10 * 1000);
32
33
  config.logger[loggerType] = (msg, options = {}) => {
33
34
  assert(!isDebugError());
34
35
  if (swallowViteLogForceOptimization(msg))
@@ -132,7 +133,7 @@ function swallowViteLogConnected() {
132
133
  globalObject.swallowViteLogConnected_originalConsoleLog = console.log;
133
134
  // The message `[vite] connected.` doesn't go through Vite's logger thus we must monkey patch console.log()
134
135
  console.log = swallowViteLogConnected_logPatch;
135
- setTimeout(swallowViteLogConnected_clean, 3000);
136
+ setTimeoutUnref(swallowViteLogConnected_clean, 3000);
136
137
  }
137
138
  // Remove console.log() monkey patch
138
139
  function swallowViteLogConnected_clean() {
@@ -1,7 +1,7 @@
1
1
  export { assertExtensionsConventions };
2
2
  export { assertExtensionsRequire };
3
3
  export { getExtensionName };
4
- import type { PlusFile } from './getPlusFilesByLocationId.js';
4
+ import type { PlusFile } from './getPlusFiles.js';
5
5
  import '../../assertEnvVite.js';
6
6
  declare function assertExtensionsConventions(plusFile: PlusFile): void;
7
7
  declare function assertExtensionsRequire(plusFiles: PlusFile[]): void;
@@ -1,8 +1,8 @@
1
- export { crawlPlusFilePaths };
1
+ export { crawlPlusFiles };
2
2
  export { isPlusFile };
3
3
  export { getPlusFileValueConfigName };
4
4
  import '../../assertEnvVite.js';
5
- declare function crawlPlusFilePaths(userRootDir: string): Promise<{
5
+ declare function crawlPlusFiles(userRootDir: string): Promise<{
6
6
  filePathAbsoluteUserRootDir: string;
7
7
  }[]>;
8
8
  declare function isPlusFile(filePath: string): boolean;
@@ -0,0 +1,71 @@
1
+ export { crawlPlusFiles };
2
+ export { isPlusFile };
3
+ export { getPlusFileValueConfigName };
4
+ import { assert, assertUsage } from '../../../../utils/assert.js';
5
+ import { assertFilePathAbsoluteFilesystem } from '../../../../utils/isFilePathAbsoluteFilesystem.js';
6
+ import { assertPosixPath } from '../../../../utils/path.js';
7
+ import path from 'node:path';
8
+ import { isTemporaryBuildFile } from './transpileAndExecuteFile.js';
9
+ import '../../assertEnvVite.js';
10
+ import { crawlFiles } from '../../../../utils/crawlFiles.js';
11
+ import { scriptFileExtensionList } from '../../../../utils/isScriptFile.js';
12
+ import { assertIsNotProductionRuntime } from '../../../../utils/assertSetup.js';
13
+ assertIsNotProductionRuntime();
14
+ async function crawlPlusFiles(userRootDir) {
15
+ assertPosixPath(userRootDir);
16
+ assertFilePathAbsoluteFilesystem(userRootDir);
17
+ let files = await crawlFiles({
18
+ filePattern: '**/+*',
19
+ fileExtension: scriptFileExtensionList,
20
+ cwd: userRootDir,
21
+ dot: false,
22
+ // Fallback to tinyglobby for users that dynamically generate plus files (and `.gitignore`s them)
23
+ globFallback: true,
24
+ });
25
+ // Filter build files
26
+ files = files.filter((filePath) => !isTemporaryBuildFile(filePath));
27
+ // Normalize
28
+ const plusFiles = files.map((filePath) => {
29
+ // Both `$ git-ls files` and tinyglobby return posix paths
30
+ assertPosixPath(filePath);
31
+ assert(!filePath.startsWith(userRootDir));
32
+ const filePathAbsoluteUserRootDir = path.posix.join('/', filePath);
33
+ assert(isPlusFile(filePathAbsoluteUserRootDir));
34
+ return { filePathAbsoluteUserRootDir };
35
+ });
36
+ return plusFiles;
37
+ }
38
+ function isPlusFile(filePath) {
39
+ assertPosixPath(filePath);
40
+ if (isTemporaryBuildFile(filePath))
41
+ return false;
42
+ const fileName = filePath.split('/').pop();
43
+ return fileName.startsWith('+');
44
+ }
45
+ function getPlusFileValueConfigName(filePath) {
46
+ if (!isPlusFile(filePath))
47
+ return null;
48
+ const fileName = path.posix.basename(filePath);
49
+ // assertNoUnexpectedPlusSign(filePath, fileName)
50
+ const basename = fileName.split('.')[0];
51
+ assert(basename.startsWith('+'));
52
+ const configName = basename.slice(1);
53
+ assertUsage(configName !== '', `${filePath} Invalid filename ${fileName}`);
54
+ return configName;
55
+ }
56
+ /* https://github.com/vikejs/vike/issues/1407
57
+ function assertNoUnexpectedPlusSign(filePath: string, fileName: string) {
58
+ const dirs = path.posix.dirname(filePath).split('/')
59
+ dirs.forEach((dir, i) => {
60
+ const dirPath = dirs.slice(0, i + 1).join('/')
61
+ assertUsage(
62
+ !dir.includes('+'),
63
+ `Character '+' is a reserved character: remove '+' from the directory name ${dirPath}/`
64
+ )
65
+ })
66
+ assertUsage(
67
+ !fileName.slice(1).includes('+'),
68
+ `Character '+' is only allowed at the beginning of filenames: make sure ${filePath} doesn't contain any '+' in its filename other than its first letter`
69
+ )
70
+ }
71
+ */
@@ -1,4 +1,4 @@
1
- export { getPlusFilesByLocationId };
1
+ export { getPlusFiles };
2
2
  export { getPlusFileFromConfigFile };
3
3
  export type { PlusFileValue };
4
4
  export type { PlusFile };
@@ -40,5 +40,5 @@ type PlusFileValue = PlusFileCommon & {
40
40
  isExtensionConfig?: undefined;
41
41
  };
42
42
  type PlusFilesByLocationId = Record<LocationId, PlusFile[]>;
43
- declare function getPlusFilesByLocationId(userRootDir: string, esbuildCache: EsbuildCache): Promise<PlusFilesByLocationId>;
43
+ declare function getPlusFiles(userRootDir: string, esbuildCache: EsbuildCache): Promise<PlusFilesByLocationId>;
44
44
  declare function getPlusFileFromConfigFile(configFile: ConfigFile, isExtensionConfig: boolean, locationId: LocationId, userRootDir: string): PlusFileConfig;
@@ -1,18 +1,17 @@
1
- export { getPlusFilesByLocationId };
1
+ export { getPlusFiles };
2
2
  export { getPlusFileFromConfigFile };
3
3
  import { assert } from '../../../../utils/assert.js';
4
4
  import { metaBuiltIn } from './metaBuiltIn.js';
5
5
  import { getLocationId } from './filesystemRouting.js';
6
- import { crawlPlusFilePaths, getPlusFileValueConfigName } from './crawlPlusFilePaths.js';
6
+ import { crawlPlusFiles, getPlusFileValueConfigName } from './crawlPlusFiles.js';
7
7
  import { getConfigFileExport } from './getConfigFileExport.js';
8
8
  import { loadConfigFile, loadValueFile } from './loadFileAtConfigTime.js';
9
9
  import { resolvePointerImport } from './resolvePointerImport.js';
10
10
  import { getFilePathResolved } from '../getFilePath.js';
11
11
  import { assertExtensionsConventions, assertExtensionsRequire } from './assertExtensions.js';
12
12
  import '../../assertEnvVite.js';
13
- // TODO/after-PR-merge rename getPlusFilesByLocationId getPlusFiles
14
- async function getPlusFilesByLocationId(userRootDir, esbuildCache) {
15
- const plusFilePaths = (await crawlPlusFilePaths(userRootDir)).map(({ filePathAbsoluteUserRootDir }) => getFilePathResolved({ filePathAbsoluteUserRootDir, userRootDir }));
13
+ async function getPlusFiles(userRootDir, esbuildCache) {
14
+ const plusFilePaths = (await crawlPlusFiles(userRootDir)).map(({ filePathAbsoluteUserRootDir }) => getFilePathResolved({ filePathAbsoluteUserRootDir, userRootDir }));
16
15
  const plusFilesByLocationId = {};
17
16
  await Promise.all(plusFilePaths.map(async (filePath) => {
18
17
  if (getPlusFileValueConfigName(filePath.filePathAbsoluteFilesystem) === 'config') {
@@ -5,7 +5,7 @@ export type { ConfigFile };
5
5
  export type { PointerImportLoaded };
6
6
  import type { FilePathResolved } from '../../../../types/FilePath.js';
7
7
  import { type EsbuildCache } from './transpileAndExecuteFile.js';
8
- import type { PlusFileValue } from './getPlusFilesByLocationId.js';
8
+ import type { PlusFileValue } from './getPlusFiles.js';
9
9
  import { PointerImport } from './resolvePointerImport.js';
10
10
  import type { ConfigDefinitionsInternal } from './metaBuiltIn.js';
11
11
  import '../../assertEnvVite.js';
@@ -327,6 +327,10 @@ const metaBuiltIn = {
327
327
  vercel: {
328
328
  env: { config: true },
329
329
  },
330
+ ai: {
331
+ env: { config: true },
332
+ global: true,
333
+ },
330
334
  };
331
335
  function getConfigEnv(pageConfig, configName) {
332
336
  const source = getConfigValueSourceRelevantAnyEnv(configName, pageConfig);
@@ -15,7 +15,7 @@ export type { PageConfigBuildTimeBeforeComputed };
15
15
  import type { PageConfigGlobalBuildTime, ConfigEnv, PageConfigBuildTime, DefinedAtFilePath } from '../../../types/PageConfig.js';
16
16
  import { type ConfigDefinitionsInternal } from './resolveVikeConfigInternal/metaBuiltIn.js';
17
17
  import { type GlobalConfigPublic } from '../../../shared-server-client/page-configs/resolveVikeConfigPublic.js';
18
- import { type PlusFile } from './resolveVikeConfigInternal/getPlusFilesByLocationId.js';
18
+ import { type PlusFile } from './resolveVikeConfigInternal/getPlusFiles.js';
19
19
  import type { PrerenderContextPublic } from '../../prerender/runPrerender.js';
20
20
  import type { ResolvedConfig, UserConfig } from 'vite';
21
21
  import { type DangerouslyUseInternals } from '../../../shared-server-client/getPublicProxy.js';
@@ -45,10 +45,10 @@ import { getFilePathResolved } from './getFilePath.js';
45
45
  import { getConfigValueBuildTime } from '../../../shared-server-client/page-configs/getConfigValueBuildTime.js';
46
46
  import { resolveGlobalConfigPublic, } from '../../../shared-server-client/page-configs/resolveVikeConfigPublic.js';
47
47
  import { getConfigValuesBase, isJsonValue, } from '../../../shared-server-client/page-configs/serialize/serializeConfigValues.js';
48
- import { getPlusFilesByLocationId, getPlusFileFromConfigFile, } from './resolveVikeConfigInternal/getPlusFilesByLocationId.js';
48
+ import { getPlusFiles, getPlusFileFromConfigFile, } from './resolveVikeConfigInternal/getPlusFiles.js';
49
49
  import { assertRouteString } from '../../../shared-server-client/route/resolveRouteString.js';
50
50
  import { getExtensionName } from './resolveVikeConfigInternal/assertExtensions.js';
51
- import { getEnvVarObject } from './getEnvVarObject.js';
51
+ import { getEnvVarObject } from '../../../utils/getEnvVarObject.js';
52
52
  import { getVikeApiOperation } from '../../../shared-server-node/api-context.js';
53
53
  import { getCliOptions } from '../../cli/context.js';
54
54
  import { resolvePrerenderConfigGlobal } from '../../prerender/resolvePrerenderConfig.js';
@@ -216,7 +216,7 @@ function hasViteConfigChanged(vikeConfigOld, vikeConfigNew) {
216
216
  return false;
217
217
  }
218
218
  async function resolveVikeConfigInternal(userRootDir, vikeVitePluginOptions, esbuildCache) {
219
- const plusFilesByLocationId = await getPlusFilesByLocationId(userRootDir, esbuildCache);
219
+ const plusFilesByLocationId = await getPlusFiles(userRootDir, esbuildCache);
220
220
  const configDefinitionsResolved = await resolveConfigDefinitions(plusFilesByLocationId, userRootDir, esbuildCache);
221
221
  const { pageConfigGlobal, pageConfigs } = getPageConfigsBuildTime(configDefinitionsResolved, plusFilesByLocationId, userRootDir);
222
222
  if (!globalObject.isV1Design_)
@@ -292,7 +292,7 @@ async function resolveConfigDefinitions(plusFilesByLocationId, userRootDir, esbu
292
292
  return configDefinitionsResolved;
293
293
  }
294
294
  // Load value files (with `env.config===true`) of *custom* configs.
295
- // - The value files of *built-in* configs are already loaded at `getPlusFilesByLocationId()`.
295
+ // - The value files of *built-in* configs are already loaded at `getPlusFiles()`.
296
296
  async function loadCustomConfigBuildTimeFiles(plusFiles, configDefinitions, userRootDir, esbuildCache) {
297
297
  const plusFileList = Object.values(plusFiles).flat(1);
298
298
  await Promise.all(plusFileList.map(async (plusFile) => {
@@ -715,7 +715,7 @@ function sortPlusFilesSameLocationId(plusFile1, plusFile2, configName) {
715
715
  return ret;
716
716
  }
717
717
  // Config set by +{configName}.js (highest precedence)
718
- // No need to make it deterministic: the overall order is already deterministic, see sortMakeDeterministic() at getPlusFilesByLocationId()
718
+ // No need to make it deterministic: the overall order is already deterministic, see sortMakeDeterministic() at getPlusFiles()
719
719
  return 0;
720
720
  }
721
721
  function resolveConfigValueSources(configName, configDef, plusFilesRelevant, userRootDir, isGlobal, plusFilesByLocationId) {
@@ -57,7 +57,7 @@ type HookNamePage = 'onHydrationEnd' | 'onBeforePrerenderStart' | 'onBeforeRende
57
57
  type HookNameGlobal = 'onBeforeRoute' | 'onPrerenderStart' | 'onCreatePageContext' | 'onCreateGlobalContext' | 'onError' | 'onHookCall';
58
58
  type HookNameOldDesign = 'render' | 'prerender' | 'onBeforePrerender';
59
59
  type ConfigNameBuiltIn = Exclude<keyof ConfigBuiltIn, keyof VikeVitePluginOptions | 'onBeforeRoute' | 'onPrerenderStart' | 'vite' | 'redirects' | 'pages'> | 'prerender' | 'hasServerOnlyHook' | 'isClientRuntimeLoaded' | 'onBeforeRenderEnv' | 'dataEnv' | 'guardEnv' | 'hooksTimeout' | 'clientHooks' | 'middleware' | 'server' | 'vercel';
60
- type ConfigNameBuiltInGlobal = 'onPrerenderStart' | 'onBeforeRoute' | 'pages' | 'prerender' | 'disableAutoFullBuild' | 'includeAssetsImportedByServer' | 'baseAssets' | 'baseServer' | 'redirects' | 'trailingSlash' | 'disableUrlNormalization' | 'vite';
60
+ type ConfigNameBuiltInGlobal = 'onPrerenderStart' | 'onBeforeRoute' | 'pages' | 'prerender' | 'ai' | 'disableAutoFullBuild' | 'includeAssetsImportedByServer' | 'baseAssets' | 'baseServer' | 'redirects' | 'trailingSlash' | 'disableUrlNormalization' | 'vite';
61
61
  type Config = ConfigBuiltIn & Vike.Config;
62
62
  /** @deprecated This type is deprecated, see:
63
63
  * - https://vike.dev/migration/hook-types
@@ -583,6 +583,26 @@ type ConfigBuiltIn = {
583
583
  * https://vike.dev/vercel
584
584
  */
585
585
  vercel?: Vercel;
586
+ /**
587
+ * Setting for Vike's AI integration.
588
+ *
589
+ * https://vike.dev/ai
590
+ */
591
+ ai?: ConfigAi;
592
+ };
593
+ type ConfigAi = {
594
+ /**
595
+ * Whether Vike automatically adds and updates `vike/SKILL.md` inside skills directories (e.g. `.agents/skills/` and `.claude/skills/`).
596
+ *
597
+ * - `true`: add `vike/SKILL.md` to every existing `skills/` directory
598
+ * - `false`: never add `vike/SKILL.md`
599
+ * - `string[]`: add `vike/SKILL.md` to exactly these directories
600
+ *
601
+ * @default true
602
+ *
603
+ * https://vike.dev/ai#skill
604
+ */
605
+ skill?: boolean | string[];
586
606
  };
587
607
  type Vercel = {
588
608
  /**
@@ -24,7 +24,7 @@ import type { ConfigValueSerialized } from '../shared-server-client/page-configs
24
24
  import type { LocationId } from '../node/vite/shared/resolveVikeConfigInternal/filesystemRouting.js';
25
25
  import type { FilePath } from './FilePath.js';
26
26
  import type { ConfigDefinitionsInternal } from '../node/vite/shared/resolveVikeConfigInternal/metaBuiltIn.js';
27
- import type { PlusFile } from '../node/vite/shared/resolveVikeConfigInternal/getPlusFilesByLocationId.js';
27
+ import type { PlusFile } from '../node/vite/shared/resolveVikeConfigInternal/getPlusFiles.js';
28
28
  import type { ApiOperation } from '../node/api/types.js';
29
29
  type PageConfigCommon = {
30
30
  pageId: string;
@@ -1 +1 @@
1
- export declare const PROJECT_VERSION: "0.4.260-commit-8a30a91";
1
+ export declare const PROJECT_VERSION: "0.4.260-commit-2e7fb2a";
@@ -1,2 +1,2 @@
1
1
  // Automatically updated by @brillout/release-me
2
- export const PROJECT_VERSION = '0.4.260-commit-8a30a91';
2
+ export const PROJECT_VERSION = '0.4.260-commit-2e7fb2a';
@@ -0,0 +1 @@
1
+ export declare const ignorePatternsBuiltIn: readonly ["**/node_modules/**", "**/.git/**", "**/ejected/**", "**/*.telefunc.*", "**/.history/**", "**/*.generated.*", "**/*.spec.*", "**/*.test.*"];
@@ -1,6 +1,6 @@
1
- import '../../../assertEnvVite.js';
2
1
  export const ignorePatternsBuiltIn = [
3
2
  '**/node_modules/**',
3
+ '**/.git/**',
4
4
  // Ejected Vike extensions, see https://github.com/snake-py/eject
5
5
  '**/ejected/**',
6
6
  // Allow:
@@ -0,0 +1,21 @@
1
+ export { crawlFiles };
2
+ declare const globstar = "**/";
3
+ type FilePattern = `${typeof globstar}${'+*' | 'skills/*/SKILL'}`;
4
+ /**
5
+ * Crawl the files matching `filePattern`, using `$ git ls-files` and, as a fallback, [tinyglobby](https://github.com/SuperchupuDev/tinyglobby).
6
+ */
7
+ declare function crawlFiles(options: {
8
+ filePattern: FilePattern;
9
+ fileExtension: readonly string[];
10
+ cwd: string;
11
+ /**
12
+ * Whether dotfiles and dot directories are crawled.
13
+ *
14
+ * Same as tinyglobby's `dot` option.
15
+ */
16
+ dot: boolean;
17
+ /**
18
+ * Whether to fallback to tinyglobby if `$ git ls-files` doesn't find any file.
19
+ */
20
+ globFallback: boolean;
21
+ }): Promise<string[]>;
@@ -0,0 +1,195 @@
1
+ export { crawlFiles };
2
+ import { assert, assertUsage, assertWarning } from './assert.js';
3
+ import { assertIsNotProductionRuntime } from './assertSetup.js';
4
+ import { isVersionMatch } from './assertVersion.js';
5
+ import { createDebug } from './debug.js';
6
+ import { deepEqual } from './deepEqual.js';
7
+ import { getGlobalObject } from './getGlobalObject.js';
8
+ import { hasProp } from './hasProp.js';
9
+ import { isNotNullish } from './isNullish.js';
10
+ import path from 'node:path';
11
+ import { glob as tinyglobby } from 'tinyglobby';
12
+ import { exec } from 'node:child_process';
13
+ import { promisify } from 'node:util';
14
+ import { getEnvVarObject } from './getEnvVarObject.js';
15
+ import pc from '@brillout/picocolors';
16
+ import picomatch from 'picomatch';
17
+ import { ignorePatternsBuiltIn } from './crawlFiles/ignorePatternsBuiltIn.js';
18
+ assertIsNotProductionRuntime();
19
+ const execA = promisify(exec);
20
+ const debug = createDebug('vike:crawl');
21
+ const globalObject = getGlobalObject('crawlFiles.ts', {
22
+ gitIsNotUsable: false,
23
+ });
24
+ const globstar = '**/';
25
+ /**
26
+ * Crawl the files matching `filePattern`, using `$ git ls-files` and, as a fallback, [tinyglobby](https://github.com/SuperchupuDev/tinyglobby).
27
+ */
28
+ async function crawlFiles(options) {
29
+ const { filePattern, fileExtension, cwd, dot, globFallback } = options;
30
+ const userSettings = getUserSettings();
31
+ const globOptions = { cwd, dot, nocase: false, ignore: getIgnorePatterns(userSettings) };
32
+ // One pattern per file extension (the `filePattern` skips the file extension)
33
+ assert(!path.posix.basename(filePattern).includes('.'));
34
+ const patterns = fileExtension.map((ext) => `${filePattern}.${ext}`);
35
+ // Crawl
36
+ const filesGit = userSettings.git !== false && (await crawlGit(patterns, cwd, globOptions));
37
+ const useGlob =
38
+ // `!filesGit` => Git isn't usable => we *have* to use tinyglobby
39
+ !filesGit ||
40
+ // `filesGit.length === 0` => fallback to tinyglobby if globFallback is true
41
+ (filesGit.length === 0 && globFallback);
42
+ const filesGlob = (useGlob || debug.isActivated) && (await crawlGlob(patterns, globOptions));
43
+ const files = useGlob ? filesGlob : filesGit;
44
+ assert(files);
45
+ if (debug.isActivated && filesGit && filesGlob) {
46
+ assertWarning(deepEqual(filesGlob.slice().sort(), filesGit.slice().sort()), "Git and glob results aren't matching.", { onlyOnce: false });
47
+ }
48
+ return files;
49
+ }
50
+ // Same as crawlGlob() but using `$ git ls-files`
51
+ async function crawlGit(patterns, cwd, globOptions) {
52
+ if (globalObject.gitIsNotUsable)
53
+ return null;
54
+ // Preserve UTF-8 file paths.
55
+ // https://github.com/vikejs/vike/issues/1658
56
+ // https://stackoverflow.com/questions/22827239/how-to-make-git-properly-display-utf-8-encoded-pathnames-in-the-console-window/22828826#22828826
57
+ // https://stackoverflow.com/questions/15884180/how-do-i-override-git-configuration-options-by-command-line-parameters/15884261#15884261
58
+ const preserveUTF8 = '-c core.quotepath=off';
59
+ const cmd = [
60
+ 'git',
61
+ preserveUTF8,
62
+ 'ls-files',
63
+ // Performance gain seems negligible: https://github.com/vikejs/vike/pull/1688#issuecomment-2166206648
64
+ ...patterns.flatMap((pattern) => {
65
+ // A leading `**/` doesn't match the root directory: we therefore add a second pattern for it — e.g. `**/+*.js` doesn't match `+config.js` while `+*.js` does
66
+ const patternRootDir = pattern.slice(globstar.length);
67
+ assert(!patternRootDir.includes(globstar)); // `**/` in the middle of the pattern isn't supported (e.g. `pages/**/+*.js` doesn't match `pages/+Page.js`)
68
+ return [`"${pattern}"`, `"${patternRootDir}"`];
69
+ }),
70
+ // Performance gain is non-negligible.
71
+ // - https://github.com/vikejs/vike/pull/1688#issuecomment-2166206648
72
+ // - When node_modules/ is untracked the performance gain could be significant?
73
+ ...globOptions.ignore.map((pattern) => `--exclude="${pattern}"`),
74
+ // --others --exclude-standard => list untracked files (--others) while using .gitignore (--exclude-standard)
75
+ // --cached => list tracked files
76
+ '--others --exclude-standard --cached',
77
+ ].join(' ');
78
+ let filesAll;
79
+ let filesDeleted;
80
+ try {
81
+ ;
82
+ [filesAll, filesDeleted] = await Promise.all([
83
+ // Main command
84
+ runCmd1(cmd, cwd),
85
+ // Get tracked but deleted files
86
+ runCmd1('git ls-files --deleted', cwd),
87
+ ]);
88
+ }
89
+ catch (err) {
90
+ if (await isGitNotUsable(cwd)) {
91
+ globalObject.gitIsNotUsable = true;
92
+ return null;
93
+ }
94
+ throw err;
95
+ }
96
+ if (debug.isActivated) {
97
+ debug('[git] cwd:', cwd);
98
+ debug('[git] cmd:', cmd);
99
+ debug('[git] result:', filesAll);
100
+ debug('[git] filesDeleted:', filesDeleted);
101
+ }
102
+ // We have to filter again here because:
103
+ // - `$ git ls-files` matches more since wildcards are deep — e.g. `+*.js` matches `pages/+some-dir/some-file.js`
104
+ // - the option --exclude of `$ git ls-files` only applies to untracked files. (We use --exclude only to speed up the `$ git ls-files` command.)
105
+ const isMatch = picomatch(patterns, globOptions);
106
+ const isDeleted = new Set(filesDeleted);
107
+ const files = filesAll.filter((filePath) => isMatch(filePath) && !isDeleted.has(filePath));
108
+ return files;
109
+ }
110
+ // Same as crawlGit() but using tinyglobby
111
+ async function crawlGlob(patterns, globOptions) {
112
+ const files = await tinyglobby(patterns, globOptions);
113
+ // Make build deterministic, in order to get a stable generated hash for dist/client/assets/entries/entry-client-routing.${hash}.js
114
+ // https://github.com/vikejs/vike/pull/1750
115
+ files.sort();
116
+ if (debug.isActivated) {
117
+ debug('[glob] patterns:', patterns);
118
+ debug('[glob] options:', globOptions);
119
+ debug('[glob] result:', files);
120
+ }
121
+ return files;
122
+ }
123
+ // Whether Git is installed and whether we can use it
124
+ async function isGitNotUsable(cwd) {
125
+ // Check Git version
126
+ {
127
+ const res = await runCmd2('git --version', cwd);
128
+ if ('err' in res)
129
+ return true;
130
+ let { stdout, stderr } = res;
131
+ assert(stderr === '');
132
+ const prefix = 'git version ';
133
+ assert(stdout.startsWith(prefix));
134
+ const gitVersion = stdout.slice(prefix.length);
135
+ // - Works with Git 2.43.1 but also (most certainly) with earlier versions.
136
+ // - We didn't bother test which is the earliest version that works.
137
+ // - Git 2.32.0 doesn't seem to work: https://github.com/vikejs/vike/discussions/1549
138
+ // - Maybe it's because of StackBlitz: looking at the release notes, Git 2.32.0 should be working.
139
+ if (!isVersionMatch(gitVersion, ['2.43.1']))
140
+ return true;
141
+ }
142
+ // Is cwd inside a Git repository?
143
+ {
144
+ const res = await runCmd2('git rev-parse --is-inside-work-tree', cwd);
145
+ if ('err' in res)
146
+ return true;
147
+ let { stdout, stderr } = res;
148
+ assert(stderr === '');
149
+ assert(stdout === 'true');
150
+ return false;
151
+ }
152
+ }
153
+ async function runCmd1(cmd, cwd) {
154
+ const { stdout } = await execA(cmd, {
155
+ cwd,
156
+ // https://github.com/vikejs/vike/issues/1982
157
+ maxBuffer: Infinity,
158
+ });
159
+ /* Not always true: https://github.com/vikejs/vike/issues/1440#issuecomment-1892831303
160
+ assert(res.stderr === '')
161
+ */
162
+ return stdout.toString().split('\n').filter(Boolean);
163
+ }
164
+ async function runCmd2(cmd, cwd) {
165
+ let res;
166
+ try {
167
+ res = await execA(cmd, { cwd });
168
+ }
169
+ catch (err) {
170
+ return { err };
171
+ }
172
+ let { stdout, stderr } = res;
173
+ stdout = stdout.toString().trim();
174
+ stderr = stderr.toString().trim();
175
+ return { stdout, stderr };
176
+ }
177
+ function getUserSettings() {
178
+ const userSettings = getEnvVarObject('VIKE_CRAWL') ?? {};
179
+ const wrongUsage = (settingName, settingType) => `Setting ${pc.cyan(settingName)} in VIKE_CRAWL should be a ${pc.cyan(settingType)}`;
180
+ assertUsage(hasProp(userSettings, 'git', 'boolean') || hasProp(userSettings, 'git', 'undefined'), wrongUsage('git', 'boolean'));
181
+ assertUsage(hasProp(userSettings, 'ignore', 'string[]') ||
182
+ hasProp(userSettings, 'ignore', 'string') ||
183
+ hasProp(userSettings, 'ignore', 'undefined'), wrongUsage('ignore', 'string or an array of strings'));
184
+ assertUsage(hasProp(userSettings, 'ignoreBuiltIn', 'boolean') || hasProp(userSettings, 'ignoreBuiltIn', 'undefined'), wrongUsage('ignoreBuiltIn', 'boolean'));
185
+ const settingNames = ['git', 'ignore', 'ignoreBuiltIn'];
186
+ Object.keys(userSettings).forEach((name) => {
187
+ assertUsage(settingNames.includes(name), `Unknown setting ${pc.bold(pc.red(name))} in VIKE_CRAWL`);
188
+ });
189
+ return userSettings;
190
+ }
191
+ function getIgnorePatterns(userSettings) {
192
+ const ignorePatternsSetByUser = [userSettings.ignore].flat().filter(isNotNullish);
193
+ const { ignoreBuiltIn } = userSettings;
194
+ return [...(ignoreBuiltIn === false ? [] : ignorePatternsBuiltIn), ...ignorePatternsSetByUser];
195
+ }
@@ -1,5 +1,4 @@
1
1
  export { getEnvVarObject };
2
2
  export { parseJson5 };
3
- import '../assertEnvVite.js';
4
3
  declare function getEnvVarObject(envVarName: 'VITE_CONFIG' | 'VIKE_CRAWL' | 'VIKE_CONFIG'): null | Record<string, unknown>;
5
4
  declare function parseJson5(valueStr: string, what: string): unknown;
@@ -1,10 +1,11 @@
1
1
  export { getEnvVarObject };
2
2
  export { parseJson5 };
3
3
  import pc from '@brillout/picocolors';
4
- import { assertUsage } from '../../../utils/assert.js';
5
- import { isObject } from '../../../utils/isObject.js';
4
+ import { assertUsage } from './assert.js';
5
+ import { isObject } from './isObject.js';
6
6
  import JSON5 from 'json5';
7
- import '../assertEnvVite.js';
7
+ import { assertIsNotProductionRuntime } from './assertSetup.js';
8
+ assertIsNotProductionRuntime();
8
9
  function getEnvVarObject(envVarName) {
9
10
  const valueStr = process.env[envVarName];
10
11
  if (!valueStr)
@@ -0,0 +1,2 @@
1
+ export { setTimeoutUnref };
2
+ declare function setTimeoutUnref(callback: () => void, milliseconds: number): ReturnType<typeof setTimeout>;
@@ -0,0 +1,11 @@
1
+ export { setTimeoutUnref };
2
+ // Same as setTimeout() but the timer never keeps the Node.js process alive (e.g. upon programmatic dev server usage that exits quickly, the process shouldn't linger because of some pending bookkeeping timer).
3
+ // - https://nodejs.org/api/timers.html#timeoutunref
4
+ // - Only use it for auxiliary timers (bookkeeping, diagnostics, background work) — never for:
5
+ // - Timers that resume the main flow (e.g. sleep()): the process could exit before the timer fires.
6
+ // - Watchdog timers (e.g. hooksTimeout, genPromise() timeout): their purpose is to fire when everything else hangs — being the last thing that keeps the process alive is their job (without them Node.js would silently exit with code 0).
7
+ function setTimeoutUnref(callback, milliseconds) {
8
+ const timeout = setTimeout(callback, milliseconds);
9
+ timeout.unref?.();
10
+ return timeout;
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike",
3
- "version": "0.4.260-commit-8a30a91",
3
+ "version": "0.4.260-commit-2e7fb2a",
4
4
  "repository": "https://github.com/vikejs/vike",
5
5
  "exports": {
6
6
  "./server": {
@@ -137,7 +137,7 @@
137
137
  "es-module-lexer": "^1.0.0",
138
138
  "esbuild": ">=0.19.0",
139
139
  "json5": "^2.0.0",
140
- "magic-string": "^1.1.0",
140
+ "magic-string": "^1.2.0",
141
141
  "picomatch": "^4.0.5",
142
142
  "semver": "^7.8.5",
143
143
  "sirv": "^3.0.2",
@@ -1,2 +0,0 @@
1
- import '../../../assertEnvVite.js';
2
- export declare const ignorePatternsBuiltIn: readonly ["**/node_modules/**", "**/ejected/**", "**/*.telefunc.*", "**/.history/**", "**/*.generated.*", "**/*.spec.*", "**/*.test.*"];
@@ -1,259 +0,0 @@
1
- export { crawlPlusFilePaths };
2
- export { isPlusFile };
3
- export { getPlusFileValueConfigName };
4
- import { assert, assertUsage, assertWarning } from '../../../../utils/assert.js';
5
- import { assertIsNotProductionRuntime } from '../../../../utils/assertSetup.js';
6
- import { isVersionMatch } from '../../../../utils/assertVersion.js';
7
- import { createDebug } from '../../../../utils/debug.js';
8
- import { deepEqual } from '../../../../utils/deepEqual.js';
9
- import { getGlobalObject } from '../../../../utils/getGlobalObject.js';
10
- import { hasProp } from '../../../../utils/hasProp.js';
11
- import { assertFilePathAbsoluteFilesystem } from '../../../../utils/isFilePathAbsoluteFilesystem.js';
12
- import { isNotNullish } from '../../../../utils/isNullish.js';
13
- import { scriptFileExtensionPattern, isScriptFile, scriptFileExtensionList } from '../../../../utils/isScriptFile.js';
14
- import { assertPosixPath } from '../../../../utils/path.js';
15
- import path from 'node:path';
16
- import { glob } from 'tinyglobby';
17
- import { exec } from 'node:child_process';
18
- import { promisify } from 'node:util';
19
- import { isTemporaryBuildFile } from './transpileAndExecuteFile.js';
20
- import { getEnvVarObject } from '../getEnvVarObject.js';
21
- import pc from '@brillout/picocolors';
22
- import picomatch from 'picomatch';
23
- import { ignorePatternsBuiltIn } from './crawlPlusFilePaths/ignorePatternsBuiltIn.js';
24
- import '../../assertEnvVite.js';
25
- const execA = promisify(exec);
26
- const debug = createDebug('vike:crawl');
27
- assertIsNotProductionRuntime();
28
- const globalObject = getGlobalObject('getVikeConfig/crawlPlusFilePaths.ts', {
29
- gitIsNotUsable: false,
30
- });
31
- // TODO/after-PR-merge rename crawlPlusFilePaths crawlPlusFiles
32
- async function crawlPlusFilePaths(userRootDir) {
33
- assertPosixPath(userRootDir);
34
- assertFilePathAbsoluteFilesystem(userRootDir);
35
- const userSettings = getUserSettings();
36
- const { ignorePatterns, ignoreMatchers } = getIgnore(userSettings);
37
- // Crawl
38
- const filesGit = userSettings.git !== false && (await gitLsFiles(userRootDir, ignorePatterns, ignoreMatchers));
39
- const filesGitNothingFound = !filesGit || filesGit.length === 0;
40
- const filesGlob = (filesGitNothingFound || debug.isActivated) && (await tinyglobby(userRootDir, ignorePatterns));
41
- let files = !filesGitNothingFound
42
- ? filesGit
43
- : // Fallback to tinyglobby for users that dynamically generate plus files. (Assuming that no plus file is found because of the user's .gitignore list.)
44
- filesGlob;
45
- assert(files);
46
- if (debug.isActivated && filesGit && filesGlob) {
47
- assertWarning(deepEqual(filesGlob.slice().sort(), filesGit.slice().sort()), "Git and glob results aren't matching.", { onlyOnce: false });
48
- }
49
- // Filter build files
50
- files = files.filter((filePath) => !isTemporaryBuildFile(filePath));
51
- // Normalize
52
- const plusFiles = files.map((filePath) => {
53
- // Both `$ git-ls files` and tinyglobby return posix paths
54
- assertPosixPath(filePath);
55
- assert(!filePath.startsWith(userRootDir));
56
- const filePathAbsoluteUserRootDir = path.posix.join('/', filePath);
57
- assert(isPlusFile(filePathAbsoluteUserRootDir));
58
- return { filePathAbsoluteUserRootDir };
59
- });
60
- return plusFiles;
61
- }
62
- // Same as tinyglobby() but using `$ git ls-files`
63
- async function gitLsFiles(userRootDir, ignorePatterns, ignoreMatchers) {
64
- if (globalObject.gitIsNotUsable)
65
- return null;
66
- // Preserve UTF-8 file paths.
67
- // https://github.com/vikejs/vike/issues/1658
68
- // https://stackoverflow.com/questions/22827239/how-to-make-git-properly-display-utf-8-encoded-pathnames-in-the-console-window/22828826#22828826
69
- // https://stackoverflow.com/questions/15884180/how-do-i-override-git-configuration-options-by-command-line-parameters/15884261#15884261
70
- const preserveUTF8 = '-c core.quotepath=off';
71
- const cmd = [
72
- 'git',
73
- preserveUTF8,
74
- 'ls-files',
75
- // Performance gain seems negligible: https://github.com/vikejs/vike/pull/1688#issuecomment-2166206648
76
- ...scriptFileExtensionList.map((ext) => `"**/+*.${ext}" "+*.${ext}"`),
77
- // Performance gain is non-negligible.
78
- // - https://github.com/vikejs/vike/pull/1688#issuecomment-2166206648
79
- // - When node_modules/ is untracked the performance gain could be significant?
80
- ...ignorePatterns.map((pattern) => `--exclude="${pattern}"`),
81
- // --others --exclude-standard => list untracked files (--others) while using .gitignore (--exclude-standard)
82
- // --cached => list tracked files
83
- '--others --exclude-standard --cached',
84
- ].join(' ');
85
- let filesAll;
86
- let filesDeleted;
87
- try {
88
- ;
89
- [filesAll, filesDeleted] = await Promise.all([
90
- // Main command
91
- runCmd1(cmd, userRootDir),
92
- // Get tracked but deleted files
93
- runCmd1('git ls-files --deleted', userRootDir),
94
- ]);
95
- }
96
- catch (err) {
97
- if (await isGitNotUsable(userRootDir)) {
98
- globalObject.gitIsNotUsable = true;
99
- return null;
100
- }
101
- throw err;
102
- }
103
- if (debug.isActivated) {
104
- debug('[git] userRootDir:', userRootDir);
105
- debug('[git] cmd:', cmd);
106
- debug('[git] result:', filesAll);
107
- debug('[git] filesDeleted:', filesDeleted);
108
- }
109
- const files = [];
110
- for (const filePath of filesAll) {
111
- // + file?
112
- if (!path.posix.basename(filePath).startsWith('+'))
113
- continue;
114
- // We have to repeat the same exclusion logic here because the option --exclude of `$ git ls-files` only applies to untracked files. (We use --exclude only to speed up the `$ git ls-files` command.)
115
- if (ignoreMatchers.some((m) => m(filePath)))
116
- continue;
117
- // JavaScript file?
118
- if (!isScriptFile(filePath))
119
- continue;
120
- // Deleted?
121
- if (filesDeleted.includes(filePath))
122
- continue;
123
- files.push(filePath);
124
- }
125
- return files;
126
- }
127
- // Same as gitLsFiles() but using tinyglobby
128
- async function tinyglobby(userRootDir, ignorePatterns) {
129
- const pattern = `**/+*.${scriptFileExtensionPattern}`;
130
- const options = {
131
- ignore: ignorePatterns,
132
- cwd: userRootDir,
133
- dot: false,
134
- };
135
- const files = await glob(pattern, options);
136
- // Make build deterministic, in order to get a stable generated hash for dist/client/assets/entries/entry-client-routing.${hash}.js
137
- // https://github.com/vikejs/vike/pull/1750
138
- files.sort();
139
- if (debug.isActivated) {
140
- debug('[glob] pattern:', pattern);
141
- debug('[glob] options:', options);
142
- debug('[glob] result:', files);
143
- }
144
- return files;
145
- }
146
- // Whether Git is installed and whether we can use it
147
- async function isGitNotUsable(userRootDir) {
148
- // Check Git version
149
- {
150
- const res = await runCmd2('git --version', userRootDir);
151
- if ('err' in res)
152
- return true;
153
- let { stdout, stderr } = res;
154
- assert(stderr === '');
155
- const prefix = 'git version ';
156
- assert(stdout.startsWith(prefix));
157
- const gitVersion = stdout.slice(prefix.length);
158
- // - Works with Git 2.43.1 but also (most certainly) with earlier versions.
159
- // - We didn't bother test which is the earliest version that works.
160
- // - Git 2.32.0 doesn't seem to work: https://github.com/vikejs/vike/discussions/1549
161
- // - Maybe it's because of StackBlitz: looking at the release notes, Git 2.32.0 should be working.
162
- if (!isVersionMatch(gitVersion, ['2.43.1']))
163
- return true;
164
- }
165
- // Is userRootDir inside a Git repository?
166
- {
167
- const res = await runCmd2('git rev-parse --is-inside-work-tree', userRootDir);
168
- if ('err' in res)
169
- return true;
170
- let { stdout, stderr } = res;
171
- assert(stderr === '');
172
- assert(stdout === 'true');
173
- return false;
174
- }
175
- }
176
- async function runCmd1(cmd, cwd) {
177
- const { stdout } = await execA(cmd, {
178
- cwd,
179
- // https://github.com/vikejs/vike/issues/1982
180
- maxBuffer: Infinity,
181
- });
182
- /* Not always true: https://github.com/vikejs/vike/issues/1440#issuecomment-1892831303
183
- assert(res.stderr === '')
184
- */
185
- return stdout.toString().split('\n').filter(Boolean);
186
- }
187
- async function runCmd2(cmd, cwd) {
188
- let res;
189
- try {
190
- res = await execA(cmd, { cwd });
191
- }
192
- catch (err) {
193
- return { err };
194
- }
195
- let { stdout, stderr } = res;
196
- stdout = stdout.toString().trim();
197
- stderr = stderr.toString().trim();
198
- return { stdout, stderr };
199
- }
200
- function getUserSettings() {
201
- const userSettings = getEnvVarObject('VIKE_CRAWL') ?? {};
202
- const wrongUsage = (settingName, settingType) => `Setting ${pc.cyan(settingName)} in VIKE_CRAWL should be a ${pc.cyan(settingType)}`;
203
- assertUsage(hasProp(userSettings, 'git', 'boolean') || hasProp(userSettings, 'git', 'undefined'), wrongUsage('git', 'boolean'));
204
- assertUsage(hasProp(userSettings, 'ignore', 'string[]') ||
205
- hasProp(userSettings, 'ignore', 'string') ||
206
- hasProp(userSettings, 'ignore', 'undefined'), wrongUsage('git', 'string or an array of strings'));
207
- assertUsage(hasProp(userSettings, 'ignoreBuiltIn', 'boolean') || hasProp(userSettings, 'ignoreBuiltIn', 'undefined'), wrongUsage('ignoreBuiltIn', 'boolean'));
208
- const settingNames = ['git', 'ignore', 'ignoreBuiltIn'];
209
- Object.keys(userSettings).forEach((name) => {
210
- assertUsage(settingNames.includes(name), `Unknown setting ${pc.bold(pc.red(name))} in VIKE_CRAWL`);
211
- });
212
- return userSettings;
213
- }
214
- function isPlusFile(filePath) {
215
- assertPosixPath(filePath);
216
- if (isTemporaryBuildFile(filePath))
217
- return false;
218
- const fileName = filePath.split('/').pop();
219
- return fileName.startsWith('+');
220
- }
221
- function getPlusFileValueConfigName(filePath) {
222
- if (!isPlusFile(filePath))
223
- return null;
224
- const fileName = path.posix.basename(filePath);
225
- // assertNoUnexpectedPlusSign(filePath, fileName)
226
- const basename = fileName.split('.')[0];
227
- assert(basename.startsWith('+'));
228
- const configName = basename.slice(1);
229
- assertUsage(configName !== '', `${filePath} Invalid filename ${fileName}`);
230
- return configName;
231
- }
232
- /* https://github.com/vikejs/vike/issues/1407
233
- function assertNoUnexpectedPlusSign(filePath: string, fileName: string) {
234
- const dirs = path.posix.dirname(filePath).split('/')
235
- dirs.forEach((dir, i) => {
236
- const dirPath = dirs.slice(0, i + 1).join('/')
237
- assertUsage(
238
- !dir.includes('+'),
239
- `Character '+' is a reserved character: remove '+' from the directory name ${dirPath}/`
240
- )
241
- })
242
- assertUsage(
243
- !fileName.slice(1).includes('+'),
244
- `Character '+' is only allowed at the beginning of filenames: make sure ${filePath} doesn't contain any '+' in its filename other than its first letter`
245
- )
246
- }
247
- */
248
- function getIgnore(userSettings) {
249
- const ignorePatternsSetByUser = [userSettings.ignore].flat().filter(isNotNullish);
250
- const { ignoreBuiltIn } = userSettings;
251
- const ignorePatterns = [...(ignoreBuiltIn === false ? [] : ignorePatternsBuiltIn), ...ignorePatternsSetByUser];
252
- const ignoreMatchers = ignorePatterns.map((p) => picomatch(p, {
253
- // We must pass the same settings than tinyglobby
254
- // https://github.com/SuperchupuDev/tinyglobby/blob/fcfb08a36c3b4d48d5488c21000c95a956d9797c/src/index.ts#L191-L194
255
- dot: false,
256
- nocase: false,
257
- }));
258
- return { ignorePatterns, ignoreMatchers };
259
- }