@intlayer/engine 9.5.9-canary.0 → 9.5.10
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/cjs/_virtual/_utils_asset.cjs +1 -1
- package/dist/cjs/build.cjs +4 -4
- package/dist/cjs/buildIntlayerDictionary/writeDynamicDictionary.cjs +2 -2
- package/dist/cjs/buildIntlayerDictionary/writeFetchDictionary.cjs +3 -3
- package/dist/cjs/buildIntlayerDictionary/writeMergedDictionary.cjs +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeNestedDictionary.cjs +3 -3
- package/dist/cjs/buildIntlayerDictionary/writeRemoteDictionary.cjs +2 -2
- package/dist/cjs/buildIntlayerDictionary/writeUnmergedDictionary.cjs +2 -2
- package/dist/cjs/cleanOutputDir.cjs +1 -1
- package/dist/cjs/cleanRemovedContentDeclaration.cjs +1 -1
- package/dist/cjs/cli.cjs +10 -3
- package/dist/cjs/createDictionaryEntryPoint/createDictionaryEntryPoint.cjs +1 -1
- package/dist/cjs/createDictionaryEntryPoint/getBuiltDictionariesPath.cjs +2 -2
- package/dist/cjs/createDictionaryEntryPoint/getBuiltDynamicDictionariesPath.cjs +2 -2
- package/dist/cjs/createDictionaryEntryPoint/getBuiltFetchDictionariesPath.cjs +2 -2
- package/dist/cjs/createDictionaryEntryPoint/getBuiltRemoteDictionariesPath.cjs +2 -2
- package/dist/cjs/createDictionaryEntryPoint/getBuiltUnmergedDictionariesPath.cjs +2 -2
- package/dist/cjs/createType/createModuleAugmentation.cjs +3 -3
- package/dist/cjs/createType/createType.cjs +1 -1
- package/dist/cjs/docReview/index.cjs +5 -5
- package/dist/cjs/docReview/pipeline.cjs +4 -4
- package/dist/cjs/docReview/reviewReport.cjs.map +1 -1
- package/dist/cjs/fetchDistantDictionaries.cjs +1 -1
- package/dist/cjs/handleContentDeclarationFileChange.cjs +2 -2
- package/dist/cjs/handleUnlinkedContentDeclarationFile.cjs +1 -1
- package/dist/cjs/init/cms.cjs +35 -0
- package/dist/cjs/init/cms.cjs.map +1 -1
- package/dist/cjs/init/documentationRouter.cjs +141 -0
- package/dist/cjs/init/documentationRouter.cjs.map +1 -0
- package/dist/cjs/init/frameworkSetup/backend/index.cjs +138 -0
- package/dist/cjs/init/frameworkSetup/backend/index.cjs.map +1 -0
- package/dist/cjs/init/frameworkSetup/backend/transforms.cjs +223 -0
- package/dist/cjs/init/frameworkSetup/backend/transforms.cjs.map +1 -0
- package/dist/cjs/init/frameworkSetup/index.cjs +9 -3
- package/dist/cjs/init/frameworkSetup/index.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/localeRouting.cjs +13 -0
- package/dist/cjs/init/frameworkSetup/localeRouting.cjs.map +1 -0
- package/dist/cjs/init/frameworkSetup/nextAppRouter/index.cjs +34 -7
- package/dist/cjs/init/frameworkSetup/nextAppRouter/index.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/restructure.cjs +1 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/templates.cjs +56 -0
- package/dist/cjs/init/frameworkSetup/nextAppRouter/templates.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs +2 -2
- package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/index.cjs +32 -3
- package/dist/cjs/init/frameworkSetup/tanstackStart/index.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/restructure.cjs +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/templates.cjs +58 -0
- package/dist/cjs/init/frameworkSetup/tanstackStart/templates.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/transforms.cjs +94 -14
- package/dist/cjs/init/frameworkSetup/tanstackStart/transforms.cjs.map +1 -1
- package/dist/cjs/init/index.cjs +46 -159
- package/dist/cjs/init/index.cjs.map +1 -1
- package/dist/cjs/init/utils/configManipulation.cjs +39 -0
- package/dist/cjs/init/utils/configManipulation.cjs.map +1 -1
- package/dist/cjs/init/utils/fileSystem.cjs +1 -1
- package/dist/cjs/init/utils/githubActions.cjs +1 -1
- package/dist/cjs/init/utils/index.cjs +10 -0
- package/dist/cjs/init/utils/intlayerDependencies.cjs +1 -1
- package/dist/cjs/init/utils/lintConfig.cjs +52 -0
- package/dist/cjs/init/utils/lintConfig.cjs.map +1 -0
- package/dist/cjs/init/utils/packageManager.cjs +63 -14
- package/dist/cjs/init/utils/packageManager.cjs.map +1 -1
- package/dist/cjs/init/utils/urlRoutingFramework.cjs +41 -0
- package/dist/cjs/init/utils/urlRoutingFramework.cjs.map +1 -0
- package/dist/cjs/initConfig/index.cjs +5 -2
- package/dist/cjs/initConfig/index.cjs.map +1 -1
- package/dist/cjs/installLSP.cjs +1 -1
- package/dist/cjs/installMCP/installMCP.cjs +1 -1
- package/dist/cjs/installSkills/index.cjs +1 -1
- package/dist/cjs/listDictionariesPath.cjs +1 -1
- package/dist/cjs/listGitFiles.cjs +1 -1
- package/dist/cjs/loadDictionaries/getIntlayerBundle.cjs +2 -2
- package/dist/cjs/loadDictionaries/index.cjs +1 -1
- package/dist/cjs/loadDictionaries/loadContentDeclaration.cjs +2 -2
- package/dist/cjs/loadDictionaries/loadDictionaries.cjs +1 -1
- package/dist/cjs/loadDictionaries/loadLocalDictionaries.cjs +1 -1
- package/dist/cjs/loadDictionaries/loadMarkdownContentDeclaration.cjs +1 -1
- package/dist/cjs/loadDictionaries/logTypeScriptErrors.cjs +104 -43
- package/dist/cjs/loadDictionaries/logTypeScriptErrors.cjs.map +1 -1
- package/dist/cjs/logConfigDetails.cjs +1 -1
- package/dist/cjs/prepareIntlayer.cjs +4 -4
- package/dist/cjs/scan/index.cjs +1 -1
- package/dist/cjs/scan/scanWebsite.cjs +1 -1
- package/dist/cjs/syncPluginKit/createFileAdapter.cjs +1 -1
- package/dist/cjs/syncPluginKit/index.cjs +1 -1
- package/dist/cjs/utils/buildComponentFilesList.cjs +1 -1
- package/dist/cjs/utils/contentWatcherLock.cjs +2 -2
- package/dist/cjs/utils/index.cjs +4 -4
- package/dist/cjs/utils/readDictionariesFromDisk.cjs +1 -1
- package/dist/cjs/utils/runOnce.cjs +1 -1
- package/dist/cjs/utils/runParallel/pidTree.cjs +1 -1
- package/dist/cjs/utils/withFileLock.cjs +1 -1
- package/dist/cjs/watcher.cjs +6 -6
- package/dist/cjs/writeConfiguration/index.cjs +1 -1
- package/dist/cjs/writeContentDeclaration/processContentDeclarationContent.cjs +2 -2
- package/dist/cjs/writeContentDeclaration/transformJSFile.cjs +1 -1
- package/dist/cjs/writeContentDeclaration/writeContentDeclaration.cjs +4 -4
- package/dist/cjs/writeContentDeclaration/writeJSFile.cjs +3 -3
- package/dist/cjs/writeContentDeclaration/writeMarkdownFile.cjs +1 -1
- package/dist/cjs/writeContentDeclaration/writeYamlFile.cjs +1 -1
- package/dist/cjs/writeFileIfChanged.cjs +1 -1
- package/dist/esm/_virtual/_utils_asset.mjs +1 -1
- package/dist/esm/build.mjs +4 -4
- package/dist/esm/buildIntlayerDictionary/writeDynamicDictionary.mjs +2 -2
- package/dist/esm/buildIntlayerDictionary/writeFetchDictionary.mjs +3 -3
- package/dist/esm/buildIntlayerDictionary/writeMergedDictionary.mjs +1 -1
- package/dist/esm/buildIntlayerDictionary/writeNestedDictionary.mjs +3 -3
- package/dist/esm/buildIntlayerDictionary/writeRemoteDictionary.mjs +2 -2
- package/dist/esm/buildIntlayerDictionary/writeUnmergedDictionary.mjs +2 -2
- package/dist/esm/cleanOutputDir.mjs +1 -1
- package/dist/esm/cleanRemovedContentDeclaration.mjs +1 -1
- package/dist/esm/cli.mjs +8 -6
- package/dist/esm/createDictionaryEntryPoint/createDictionaryEntryPoint.mjs +1 -1
- package/dist/esm/createDictionaryEntryPoint/getBuiltDictionariesPath.mjs +2 -2
- package/dist/esm/createDictionaryEntryPoint/getBuiltDynamicDictionariesPath.mjs +2 -2
- package/dist/esm/createDictionaryEntryPoint/getBuiltFetchDictionariesPath.mjs +2 -2
- package/dist/esm/createDictionaryEntryPoint/getBuiltRemoteDictionariesPath.mjs +2 -2
- package/dist/esm/createDictionaryEntryPoint/getBuiltUnmergedDictionariesPath.mjs +2 -2
- package/dist/esm/createType/createModuleAugmentation.mjs +3 -3
- package/dist/esm/createType/createType.mjs +1 -1
- package/dist/esm/docReview/index.mjs +5 -5
- package/dist/esm/docReview/pipeline.mjs +4 -4
- package/dist/esm/docReview/reviewReport.mjs.map +1 -1
- package/dist/esm/fetchDistantDictionaries.mjs +1 -1
- package/dist/esm/handleContentDeclarationFileChange.mjs +2 -2
- package/dist/esm/handleUnlinkedContentDeclarationFile.mjs +1 -1
- package/dist/esm/init/cms.mjs +35 -2
- package/dist/esm/init/cms.mjs.map +1 -1
- package/dist/esm/init/documentationRouter.mjs +139 -0
- package/dist/esm/init/documentationRouter.mjs.map +1 -0
- package/dist/esm/init/frameworkSetup/backend/index.mjs +130 -0
- package/dist/esm/init/frameworkSetup/backend/index.mjs.map +1 -0
- package/dist/esm/init/frameworkSetup/backend/transforms.mjs +216 -0
- package/dist/esm/init/frameworkSetup/backend/transforms.mjs.map +1 -0
- package/dist/esm/init/frameworkSetup/index.mjs +9 -3
- package/dist/esm/init/frameworkSetup/index.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/localeRouting.mjs +12 -0
- package/dist/esm/init/frameworkSetup/localeRouting.mjs.map +1 -0
- package/dist/esm/init/frameworkSetup/nextAppRouter/index.mjs +36 -9
- package/dist/esm/init/frameworkSetup/nextAppRouter/index.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/restructure.mjs +1 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/templates.mjs +54 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/templates.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs +2 -2
- package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/index.mjs +35 -6
- package/dist/esm/init/frameworkSetup/tanstackStart/index.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/restructure.mjs +1 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/templates.mjs +56 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/templates.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/transforms.mjs +94 -15
- package/dist/esm/init/frameworkSetup/tanstackStart/transforms.mjs.map +1 -1
- package/dist/esm/init/index.mjs +42 -158
- package/dist/esm/init/index.mjs.map +1 -1
- package/dist/esm/init/utils/configManipulation.mjs +38 -1
- package/dist/esm/init/utils/configManipulation.mjs.map +1 -1
- package/dist/esm/init/utils/fileSystem.mjs +1 -1
- package/dist/esm/init/utils/githubActions.mjs +1 -1
- package/dist/esm/init/utils/index.mjs +5 -3
- package/dist/esm/init/utils/intlayerDependencies.mjs +1 -1
- package/dist/esm/init/utils/lintConfig.mjs +50 -0
- package/dist/esm/init/utils/lintConfig.mjs.map +1 -0
- package/dist/esm/init/utils/packageManager.mjs +61 -15
- package/dist/esm/init/utils/packageManager.mjs.map +1 -1
- package/dist/esm/init/utils/urlRoutingFramework.mjs +40 -0
- package/dist/esm/init/utils/urlRoutingFramework.mjs.map +1 -0
- package/dist/esm/initConfig/index.mjs +5 -2
- package/dist/esm/initConfig/index.mjs.map +1 -1
- package/dist/esm/installLSP.mjs +1 -1
- package/dist/esm/installMCP/installMCP.mjs +1 -1
- package/dist/esm/installSkills/index.mjs +1 -1
- package/dist/esm/listDictionariesPath.mjs +1 -1
- package/dist/esm/listGitFiles.mjs +1 -1
- package/dist/esm/loadDictionaries/getIntlayerBundle.mjs +2 -2
- package/dist/esm/loadDictionaries/index.mjs +1 -1
- package/dist/esm/loadDictionaries/loadContentDeclaration.mjs +2 -2
- package/dist/esm/loadDictionaries/loadDictionaries.mjs +1 -1
- package/dist/esm/loadDictionaries/loadLocalDictionaries.mjs +1 -1
- package/dist/esm/loadDictionaries/loadMarkdownContentDeclaration.mjs +1 -1
- package/dist/esm/loadDictionaries/logTypeScriptErrors.mjs +105 -44
- package/dist/esm/loadDictionaries/logTypeScriptErrors.mjs.map +1 -1
- package/dist/esm/logConfigDetails.mjs +1 -1
- package/dist/esm/prepareIntlayer.mjs +4 -4
- package/dist/esm/scan/index.mjs +1 -1
- package/dist/esm/scan/scanWebsite.mjs +1 -1
- package/dist/esm/syncPluginKit/createFileAdapter.mjs +1 -1
- package/dist/esm/syncPluginKit/index.mjs +1 -1
- package/dist/esm/utils/buildComponentFilesList.mjs +1 -1
- package/dist/esm/utils/contentWatcherLock.mjs +2 -2
- package/dist/esm/utils/index.mjs +4 -4
- package/dist/esm/utils/readDictionariesFromDisk.mjs +1 -1
- package/dist/esm/utils/runOnce.mjs +1 -1
- package/dist/esm/utils/runParallel/pidTree.mjs +1 -1
- package/dist/esm/utils/withFileLock.mjs +1 -1
- package/dist/esm/watcher.mjs +6 -6
- package/dist/esm/writeConfiguration/index.mjs +1 -1
- package/dist/esm/writeContentDeclaration/processContentDeclarationContent.mjs +2 -2
- package/dist/esm/writeContentDeclaration/transformJSFile.mjs +1 -1
- package/dist/esm/writeContentDeclaration/writeContentDeclaration.mjs +4 -4
- package/dist/esm/writeContentDeclaration/writeJSFile.mjs +3 -3
- package/dist/esm/writeContentDeclaration/writeMarkdownFile.mjs +1 -1
- package/dist/esm/writeContentDeclaration/writeYamlFile.mjs +1 -1
- package/dist/esm/writeFileIfChanged.mjs +1 -1
- package/dist/types/cli.d.ts +5 -3
- package/dist/types/docReview/reviewReport.d.ts.map +1 -1
- package/dist/types/formatDictionary.d.ts +2 -2
- package/dist/types/init/cms.d.ts +11 -0
- package/dist/types/init/cms.d.ts.map +1 -1
- package/dist/types/init/documentationRouter.d.ts +64 -0
- package/dist/types/init/documentationRouter.d.ts.map +1 -0
- package/dist/types/init/frameworkSetup/backend/index.d.ts +16 -0
- package/dist/types/init/frameworkSetup/backend/index.d.ts.map +1 -0
- package/dist/types/init/frameworkSetup/backend/transforms.d.ts +37 -0
- package/dist/types/init/frameworkSetup/backend/transforms.d.ts.map +1 -0
- package/dist/types/init/frameworkSetup/index.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/localeRouting.d.ts +11 -0
- package/dist/types/init/frameworkSetup/localeRouting.d.ts.map +1 -0
- package/dist/types/init/frameworkSetup/nextAppRouter/index.d.ts +5 -5
- package/dist/types/init/frameworkSetup/nextAppRouter/index.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/templates.d.ts +12 -0
- package/dist/types/init/frameworkSetup/nextAppRouter/templates.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts +10 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/index.d.ts +3 -2
- package/dist/types/init/frameworkSetup/tanstackStart/index.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/templates.d.ts +10 -0
- package/dist/types/init/frameworkSetup/tanstackStart/templates.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/transforms.d.ts +12 -0
- package/dist/types/init/frameworkSetup/tanstackStart/transforms.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/types.d.ts +6 -0
- package/dist/types/init/frameworkSetup/types.d.ts.map +1 -1
- package/dist/types/init/index.d.ts +15 -1
- package/dist/types/init/index.d.ts.map +1 -1
- package/dist/types/init/utils/configManipulation.d.ts +14 -0
- package/dist/types/init/utils/configManipulation.d.ts.map +1 -1
- package/dist/types/init/utils/index.d.ts +5 -3
- package/dist/types/init/utils/lintConfig.d.ts +28 -0
- package/dist/types/init/utils/lintConfig.d.ts.map +1 -0
- package/dist/types/init/utils/packageManager.d.ts +29 -6
- package/dist/types/init/utils/packageManager.d.ts.map +1 -1
- package/dist/types/init/utils/urlRoutingFramework.d.ts +9 -0
- package/dist/types/init/utils/urlRoutingFramework.d.ts.map +1 -0
- package/dist/types/initConfig/index.d.ts +3 -1
- package/dist/types/initConfig/index.d.ts.map +1 -1
- package/dist/types/loadDictionaries/logTypeScriptErrors.d.ts +4 -0
- package/dist/types/loadDictionaries/logTypeScriptErrors.d.ts.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { babelTsParser } from "../../../utils/babelParser.mjs";
|
|
2
|
+
import { ensureNamedImport, isModuleScopeBinding } from "../../utils/astImports.mjs";
|
|
3
|
+
import * as recast from "recast";
|
|
4
|
+
|
|
5
|
+
//#region src/init/frameworkSetup/backend/transforms.ts
|
|
6
|
+
const { namedTypes: n } = recast.types;
|
|
7
|
+
/** Local name the injected middleware / plugin factory is bound to. */
|
|
8
|
+
const MIDDLEWARE_IDENTIFIER = "intlayer";
|
|
9
|
+
/** babel-ts parser handles TypeScript *and* plain JavaScript. */
|
|
10
|
+
const parseCode = (code) => recast.parse(code, { parser: babelTsParser });
|
|
11
|
+
/** Parses a single statement. */
|
|
12
|
+
const parseStatement = (code) => parseCode(code).program.body[0];
|
|
13
|
+
/** Whether `node` is a `require("<source>")` call. */
|
|
14
|
+
const isRequireOf = (node, source) => node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "require" && node.arguments[0]?.value === source;
|
|
15
|
+
/**
|
|
16
|
+
* Resolves the local names a factory is bound to at module scope, from ESM
|
|
17
|
+
* imports (`import express from "express"`, `import { Hono } from "hono"`) or
|
|
18
|
+
* CommonJS requires (`const express = require("express")`,
|
|
19
|
+
* `const { Hono } = require("hono")`).
|
|
20
|
+
*/
|
|
21
|
+
const findFactoryLocalNames = (ast, factory) => {
|
|
22
|
+
const localNames = [];
|
|
23
|
+
const acceptsNamed = (name) => factory.named?.includes(name) ?? false;
|
|
24
|
+
for (const statement of ast.program.body) {
|
|
25
|
+
if (statement.type === "ImportDeclaration" && statement.source.value === factory.source) for (const specifier of statement.specifiers) {
|
|
26
|
+
const isAcceptedDefault = specifier.type === "ImportDefaultSpecifier" && factory.default;
|
|
27
|
+
const isAcceptedNamed = specifier.type === "ImportSpecifier" && acceptsNamed(specifier.imported.name);
|
|
28
|
+
if (isAcceptedDefault || isAcceptedNamed) localNames.push(specifier.local.name);
|
|
29
|
+
}
|
|
30
|
+
if (statement.type !== "VariableDeclaration") continue;
|
|
31
|
+
for (const declarator of statement.declarations) {
|
|
32
|
+
if (!isRequireOf(declarator.init, factory.source)) continue;
|
|
33
|
+
if (declarator.id.type === "Identifier" && factory.default) localNames.push(declarator.id.name);
|
|
34
|
+
if (declarator.id.type === "ObjectPattern") {
|
|
35
|
+
for (const property of declarator.id.properties) if (property.key?.type === "Identifier" && property.value?.type === "Identifier" && acceptsNamed(property.key.name)) localNames.push(property.value.name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return localNames;
|
|
40
|
+
};
|
|
41
|
+
/** Whether the module uses CommonJS (`require`) rather than ESM imports. */
|
|
42
|
+
const isCommonJsModule = (ast) => !ast.program.body.some((statement) => statement.type === "ImportDeclaration" || statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration") && ast.program.body.some((statement) => statement.type === "VariableDeclaration" && statement.declarations.some((declarator) => declarator.init?.type === "CallExpression" && declarator.init.callee?.name === "require"));
|
|
43
|
+
/**
|
|
44
|
+
* Binds `intlayer` from `source`: an ESM named import, or — in a CommonJS
|
|
45
|
+
* module — `const { intlayer } = require("<source>");` placed after the last
|
|
46
|
+
* top-level `require`.
|
|
47
|
+
*/
|
|
48
|
+
const ensureMiddlewareBinding = (ast, source) => {
|
|
49
|
+
if (!isCommonJsModule(ast)) {
|
|
50
|
+
ensureNamedImport(ast, MIDDLEWARE_IDENTIFIER, source);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const body = ast.program.body;
|
|
54
|
+
let insertIndex = 0;
|
|
55
|
+
body.forEach((statement, index) => {
|
|
56
|
+
if (statement.type === "VariableDeclaration" && statement.declarations.some((declarator) => declarator.init?.callee?.name === "require")) insertIndex = index + 1;
|
|
57
|
+
});
|
|
58
|
+
body.splice(insertIndex, 0, parseStatement(`const { ${MIDDLEWARE_IDENTIFIER} } = require("${source}");`));
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Finds the first `const <app> = <init>` whose initializer matches, returning
|
|
62
|
+
* the app identifier and the statement path to insert after. `export const`
|
|
63
|
+
* declarations resolve to the export statement.
|
|
64
|
+
*/
|
|
65
|
+
const findAppDeclaration = (ast, matchesInit) => {
|
|
66
|
+
let found = null;
|
|
67
|
+
recast.visit(ast, { visitVariableDeclarator(path) {
|
|
68
|
+
const { id, init } = path.node;
|
|
69
|
+
if (!found && id.type === "Identifier" && init && matchesInit(init)) {
|
|
70
|
+
let statementPath = path.parent;
|
|
71
|
+
if (statementPath.parent?.node.type === "ExportNamedDeclaration") statementPath = statementPath.parent;
|
|
72
|
+
found = {
|
|
73
|
+
appName: id.name,
|
|
74
|
+
statementPath
|
|
75
|
+
};
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
this.traverse(path);
|
|
79
|
+
} });
|
|
80
|
+
return found;
|
|
81
|
+
};
|
|
82
|
+
/** Whether `node` calls one of `calleeNames` directly (`express()`, `new Hono()`). */
|
|
83
|
+
const isFactoryCall = (node, calleeNames, type) => node?.type === type && node.callee?.type === "Identifier" && calleeNames.includes(node.callee.name);
|
|
84
|
+
/**
|
|
85
|
+
* Shared flow of the "declare the app, then register the middleware on the
|
|
86
|
+
* next line" frameworks (Express, Fastify, Hono, NestJS).
|
|
87
|
+
*/
|
|
88
|
+
const registerAfterAppDeclaration = (code, { middlewareSource, findDeclaration, buildRegistration }) => {
|
|
89
|
+
if (code.includes(middlewareSource)) return {
|
|
90
|
+
code,
|
|
91
|
+
status: "already"
|
|
92
|
+
};
|
|
93
|
+
const ast = parseCode(code);
|
|
94
|
+
if (isModuleScopeBinding(ast, MIDDLEWARE_IDENTIFIER)) return {
|
|
95
|
+
code,
|
|
96
|
+
status: "skipped"
|
|
97
|
+
};
|
|
98
|
+
const declaration = findDeclaration(ast);
|
|
99
|
+
if (!declaration) return {
|
|
100
|
+
code,
|
|
101
|
+
status: "skipped"
|
|
102
|
+
};
|
|
103
|
+
declaration.statementPath.insertAfter(parseStatement(buildRegistration(declaration.appName)));
|
|
104
|
+
ensureMiddlewareBinding(ast, middlewareSource);
|
|
105
|
+
return {
|
|
106
|
+
code: recast.print(ast).code,
|
|
107
|
+
status: "registered"
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Registers `express-intlayer` right after `const app = express()`:
|
|
112
|
+
* `app.use(intlayer());`.
|
|
113
|
+
*/
|
|
114
|
+
const registerExpressMiddleware = (code) => registerAfterAppDeclaration(code, {
|
|
115
|
+
middlewareSource: "express-intlayer",
|
|
116
|
+
findDeclaration: (ast) => {
|
|
117
|
+
const factoryNames = findFactoryLocalNames(ast, {
|
|
118
|
+
source: "express",
|
|
119
|
+
default: true
|
|
120
|
+
});
|
|
121
|
+
return findAppDeclaration(ast, (init) => isFactoryCall(init, factoryNames, "CallExpression"));
|
|
122
|
+
},
|
|
123
|
+
buildRegistration: (appName) => `${appName}.use(intlayer());`
|
|
124
|
+
});
|
|
125
|
+
/**
|
|
126
|
+
* Registers `fastify-intlayer` right after `const app = Fastify()`:
|
|
127
|
+
* `app.register(intlayer);`. Not awaited, so it also works in CommonJS and
|
|
128
|
+
* non-async scopes — Fastify queues plugins until `ready`/`listen`.
|
|
129
|
+
*/
|
|
130
|
+
const registerFastifyPlugin = (code) => registerAfterAppDeclaration(code, {
|
|
131
|
+
middlewareSource: "fastify-intlayer",
|
|
132
|
+
findDeclaration: (ast) => {
|
|
133
|
+
const factoryNames = findFactoryLocalNames(ast, {
|
|
134
|
+
source: "fastify",
|
|
135
|
+
default: true,
|
|
136
|
+
named: ["fastify", "Fastify"]
|
|
137
|
+
});
|
|
138
|
+
return findAppDeclaration(ast, (init) => isFactoryCall(init, factoryNames, "CallExpression"));
|
|
139
|
+
},
|
|
140
|
+
buildRegistration: (appName) => `${appName}.register(intlayer);`
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* Registers `hono-intlayer` right after `const app = new Hono()`:
|
|
144
|
+
* `app.use("*", intlayer());`. A chained declaration
|
|
145
|
+
* (`new Hono().get(…)`) is skipped: its routes would be registered before the
|
|
146
|
+
* middleware, which Hono runs in registration order.
|
|
147
|
+
*/
|
|
148
|
+
const registerHonoMiddleware = (code) => registerAfterAppDeclaration(code, {
|
|
149
|
+
middlewareSource: "hono-intlayer",
|
|
150
|
+
findDeclaration: (ast) => {
|
|
151
|
+
const factoryNames = findFactoryLocalNames(ast, {
|
|
152
|
+
source: "hono",
|
|
153
|
+
named: ["Hono"]
|
|
154
|
+
});
|
|
155
|
+
return findAppDeclaration(ast, (init) => isFactoryCall(init, factoryNames, "NewExpression"));
|
|
156
|
+
},
|
|
157
|
+
buildRegistration: (appName) => `${appName}.use("*", intlayer());`
|
|
158
|
+
});
|
|
159
|
+
/**
|
|
160
|
+
* Registers `express-intlayer` on a NestJS (Express platform) app right after
|
|
161
|
+
* `const app = await NestFactory.create(…)`: `app.use(intlayer());`.
|
|
162
|
+
*/
|
|
163
|
+
const registerNestJsMiddleware = (code) => registerAfterAppDeclaration(code, {
|
|
164
|
+
middlewareSource: "express-intlayer",
|
|
165
|
+
findDeclaration: (ast) => findAppDeclaration(ast, (init) => {
|
|
166
|
+
const call = init.type === "AwaitExpression" ? init.argument : init;
|
|
167
|
+
return n.CallExpression.check(call) && n.MemberExpression.check(call.callee) && call.callee.object.name === "NestFactory" && call.callee.property.name === "create";
|
|
168
|
+
}),
|
|
169
|
+
buildRegistration: (appName) => `${appName}.use(intlayer());`
|
|
170
|
+
});
|
|
171
|
+
/**
|
|
172
|
+
* Registers `elysia-intlayer` directly on the app constructor:
|
|
173
|
+
* `new Elysia()` → `new Elysia().use(intlayer())`. Works for chained
|
|
174
|
+
* declarations too, and keeps the plugin ahead of every route.
|
|
175
|
+
*/
|
|
176
|
+
const registerElysiaPlugin = (code) => {
|
|
177
|
+
const middlewareSource = "elysia-intlayer";
|
|
178
|
+
if (code.includes(middlewareSource)) return {
|
|
179
|
+
code,
|
|
180
|
+
status: "already"
|
|
181
|
+
};
|
|
182
|
+
const ast = parseCode(code);
|
|
183
|
+
if (isModuleScopeBinding(ast, MIDDLEWARE_IDENTIFIER)) return {
|
|
184
|
+
code,
|
|
185
|
+
status: "skipped"
|
|
186
|
+
};
|
|
187
|
+
const factoryNames = findFactoryLocalNames(ast, {
|
|
188
|
+
source: "elysia",
|
|
189
|
+
named: ["Elysia"]
|
|
190
|
+
});
|
|
191
|
+
let registered = false;
|
|
192
|
+
recast.visit(ast, { visitNewExpression(path) {
|
|
193
|
+
if (registered) return false;
|
|
194
|
+
if (isFactoryCall(path.node, factoryNames, "NewExpression")) {
|
|
195
|
+
const withPlugin = parseStatement("__app__.use(intlayer());").expression;
|
|
196
|
+
withPlugin.callee.object = path.node;
|
|
197
|
+
path.replace(withPlugin);
|
|
198
|
+
registered = true;
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
this.traverse(path);
|
|
202
|
+
} });
|
|
203
|
+
if (!registered) return {
|
|
204
|
+
code,
|
|
205
|
+
status: "skipped"
|
|
206
|
+
};
|
|
207
|
+
ensureMiddlewareBinding(ast, middlewareSource);
|
|
208
|
+
return {
|
|
209
|
+
code: recast.print(ast).code,
|
|
210
|
+
status: "registered"
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
//#endregion
|
|
215
|
+
export { registerElysiaPlugin, registerExpressMiddleware, registerFastifyPlugin, registerHonoMiddleware, registerNestJsMiddleware };
|
|
216
|
+
//# sourceMappingURL=transforms.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transforms.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/backend/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\nimport { babelTsParser } from '../../../utils/babelParser';\nimport {\n ensureNamedImport,\n isModuleScopeBinding,\n} from '../../utils/astImports';\n\nconst { namedTypes: n } = recast.types;\n\n/** Local name the injected middleware / plugin factory is bound to. */\nconst MIDDLEWARE_IDENTIFIER = 'intlayer';\n\n/** babel-ts parser handles TypeScript *and* plain JavaScript. */\nconst parseCode = (code: string): any =>\n recast.parse(code, { parser: babelTsParser });\n\n/** Parses a single statement. */\nconst parseStatement = (code: string): any => parseCode(code).program.body[0];\n\n/** Result of a backend transform. `code` is unchanged for any non-`registered` status. */\nexport type BackendTransformResult = {\n code: string;\n status: 'registered' | 'already' | 'skipped';\n};\n\n/** How a server framework exposes its app factory. */\ntype FactoryImport = {\n /** Module the factory is imported from, e.g. `express`. */\n source: string;\n /** Accepted forms: the default export, and/or these named exports. */\n default?: boolean;\n named?: string[];\n};\n\n/** Whether `node` is a `require(\"<source>\")` call. */\nconst isRequireOf = (node: any, source: string): boolean =>\n node?.type === 'CallExpression' &&\n node.callee?.type === 'Identifier' &&\n node.callee.name === 'require' &&\n node.arguments[0]?.value === source;\n\n/**\n * Resolves the local names a factory is bound to at module scope, from ESM\n * imports (`import express from \"express\"`, `import { Hono } from \"hono\"`) or\n * CommonJS requires (`const express = require(\"express\")`,\n * `const { Hono } = require(\"hono\")`).\n */\nconst findFactoryLocalNames = (ast: any, factory: FactoryImport): string[] => {\n const localNames: string[] = [];\n const acceptsNamed = (name: string): boolean =>\n factory.named?.includes(name) ?? false;\n\n for (const statement of ast.program.body) {\n if (\n statement.type === 'ImportDeclaration' &&\n statement.source.value === factory.source\n ) {\n for (const specifier of statement.specifiers) {\n const isAcceptedDefault =\n specifier.type === 'ImportDefaultSpecifier' && factory.default;\n const isAcceptedNamed =\n specifier.type === 'ImportSpecifier' &&\n acceptsNamed(specifier.imported.name);\n if (isAcceptedDefault || isAcceptedNamed) {\n localNames.push(specifier.local.name);\n }\n }\n }\n\n if (statement.type !== 'VariableDeclaration') continue;\n\n for (const declarator of statement.declarations) {\n if (!isRequireOf(declarator.init, factory.source)) continue;\n\n if (declarator.id.type === 'Identifier' && factory.default) {\n localNames.push(declarator.id.name);\n }\n if (declarator.id.type === 'ObjectPattern') {\n for (const property of declarator.id.properties) {\n if (\n property.key?.type === 'Identifier' &&\n property.value?.type === 'Identifier' &&\n acceptsNamed(property.key.name)\n ) {\n localNames.push(property.value.name);\n }\n }\n }\n }\n }\n\n return localNames;\n};\n\n/** Whether the module uses CommonJS (`require`) rather than ESM imports. */\nconst isCommonJsModule = (ast: any): boolean =>\n !ast.program.body.some(\n (statement: any) =>\n statement.type === 'ImportDeclaration' ||\n statement.type === 'ExportNamedDeclaration' ||\n statement.type === 'ExportDefaultDeclaration'\n ) &&\n ast.program.body.some(\n (statement: any) =>\n statement.type === 'VariableDeclaration' &&\n statement.declarations.some(\n (declarator: any) =>\n declarator.init?.type === 'CallExpression' &&\n declarator.init.callee?.name === 'require'\n )\n );\n\n/**\n * Binds `intlayer` from `source`: an ESM named import, or — in a CommonJS\n * module — `const { intlayer } = require(\"<source>\");` placed after the last\n * top-level `require`.\n */\nconst ensureMiddlewareBinding = (ast: any, source: string): void => {\n if (!isCommonJsModule(ast)) {\n ensureNamedImport(ast, MIDDLEWARE_IDENTIFIER, source);\n return;\n }\n\n const body = ast.program.body;\n let insertIndex = 0;\n body.forEach((statement: any, index: number) => {\n const isRequireDeclaration =\n statement.type === 'VariableDeclaration' &&\n statement.declarations.some(\n (declarator: any) => declarator.init?.callee?.name === 'require'\n );\n if (isRequireDeclaration) insertIndex = index + 1;\n });\n\n body.splice(\n insertIndex,\n 0,\n parseStatement(`const { ${MIDDLEWARE_IDENTIFIER} } = require(\"${source}\");`)\n );\n};\n\n/**\n * Finds the first `const <app> = <init>` whose initializer matches, returning\n * the app identifier and the statement path to insert after. `export const`\n * declarations resolve to the export statement.\n */\nconst findAppDeclaration = (\n ast: any,\n matchesInit: (init: any) => boolean\n): { appName: string; statementPath: any } | null => {\n let found: { appName: string; statementPath: any } | null = null;\n\n recast.visit(ast, {\n visitVariableDeclarator(path) {\n const { id, init } = path.node as any;\n if (!found && id.type === 'Identifier' && init && matchesInit(init)) {\n let statementPath = path.parent;\n if (statementPath.parent?.node.type === 'ExportNamedDeclaration') {\n statementPath = statementPath.parent;\n }\n found = { appName: id.name, statementPath };\n return false;\n }\n this.traverse(path);\n },\n });\n\n return found;\n};\n\n/** Whether `node` calls one of `calleeNames` directly (`express()`, `new Hono()`). */\nconst isFactoryCall = (\n node: any,\n calleeNames: string[],\n type: 'CallExpression' | 'NewExpression'\n): boolean =>\n node?.type === type &&\n node.callee?.type === 'Identifier' &&\n calleeNames.includes(node.callee.name);\n\n/**\n * Shared flow of the \"declare the app, then register the middleware on the\n * next line\" frameworks (Express, Fastify, Hono, NestJS).\n */\nconst registerAfterAppDeclaration = (\n code: string,\n {\n middlewareSource,\n findDeclaration,\n buildRegistration,\n }: {\n middlewareSource: string;\n findDeclaration: (ast: any) => ReturnType<typeof findAppDeclaration>;\n buildRegistration: (appName: string) => string;\n }\n): BackendTransformResult => {\n if (code.includes(middlewareSource)) return { code, status: 'already' };\n\n const ast = parseCode(code);\n if (isModuleScopeBinding(ast, MIDDLEWARE_IDENTIFIER)) {\n return { code, status: 'skipped' };\n }\n\n const declaration = findDeclaration(ast);\n if (!declaration) return { code, status: 'skipped' };\n\n declaration.statementPath.insertAfter(\n parseStatement(buildRegistration(declaration.appName))\n );\n ensureMiddlewareBinding(ast, middlewareSource);\n\n return { code: recast.print(ast).code, status: 'registered' };\n};\n\n/**\n * Registers `express-intlayer` right after `const app = express()`:\n * `app.use(intlayer());`.\n */\nexport const registerExpressMiddleware = (\n code: string\n): BackendTransformResult =>\n registerAfterAppDeclaration(code, {\n middlewareSource: 'express-intlayer',\n findDeclaration: (ast) => {\n const factoryNames = findFactoryLocalNames(ast, {\n source: 'express',\n default: true,\n });\n return findAppDeclaration(ast, (init) =>\n isFactoryCall(init, factoryNames, 'CallExpression')\n );\n },\n buildRegistration: (appName) => `${appName}.use(intlayer());`,\n });\n\n/**\n * Registers `fastify-intlayer` right after `const app = Fastify()`:\n * `app.register(intlayer);`. Not awaited, so it also works in CommonJS and\n * non-async scopes — Fastify queues plugins until `ready`/`listen`.\n */\nexport const registerFastifyPlugin = (code: string): BackendTransformResult =>\n registerAfterAppDeclaration(code, {\n middlewareSource: 'fastify-intlayer',\n findDeclaration: (ast) => {\n const factoryNames = findFactoryLocalNames(ast, {\n source: 'fastify',\n default: true,\n named: ['fastify', 'Fastify'],\n });\n return findAppDeclaration(ast, (init) =>\n isFactoryCall(init, factoryNames, 'CallExpression')\n );\n },\n buildRegistration: (appName) => `${appName}.register(intlayer);`,\n });\n\n/**\n * Registers `hono-intlayer` right after `const app = new Hono()`:\n * `app.use(\"*\", intlayer());`. A chained declaration\n * (`new Hono().get(…)`) is skipped: its routes would be registered before the\n * middleware, which Hono runs in registration order.\n */\nexport const registerHonoMiddleware = (code: string): BackendTransformResult =>\n registerAfterAppDeclaration(code, {\n middlewareSource: 'hono-intlayer',\n findDeclaration: (ast) => {\n const factoryNames = findFactoryLocalNames(ast, {\n source: 'hono',\n named: ['Hono'],\n });\n return findAppDeclaration(ast, (init) =>\n isFactoryCall(init, factoryNames, 'NewExpression')\n );\n },\n buildRegistration: (appName) => `${appName}.use(\"*\", intlayer());`,\n });\n\n/**\n * Registers `express-intlayer` on a NestJS (Express platform) app right after\n * `const app = await NestFactory.create(…)`: `app.use(intlayer());`.\n */\nexport const registerNestJsMiddleware = (\n code: string\n): BackendTransformResult =>\n registerAfterAppDeclaration(code, {\n middlewareSource: 'express-intlayer',\n findDeclaration: (ast) =>\n findAppDeclaration(ast, (init) => {\n const call = init.type === 'AwaitExpression' ? init.argument : init;\n return (\n n.CallExpression.check(call) &&\n n.MemberExpression.check(call.callee) &&\n (call.callee.object as any).name === 'NestFactory' &&\n (call.callee.property as any).name === 'create'\n );\n }),\n buildRegistration: (appName) => `${appName}.use(intlayer());`,\n });\n\n/**\n * Registers `elysia-intlayer` directly on the app constructor:\n * `new Elysia()` → `new Elysia().use(intlayer())`. Works for chained\n * declarations too, and keeps the plugin ahead of every route.\n */\nexport const registerElysiaPlugin = (code: string): BackendTransformResult => {\n const middlewareSource = 'elysia-intlayer';\n if (code.includes(middlewareSource)) return { code, status: 'already' };\n\n const ast = parseCode(code);\n if (isModuleScopeBinding(ast, MIDDLEWARE_IDENTIFIER)) {\n return { code, status: 'skipped' };\n }\n\n const factoryNames = findFactoryLocalNames(ast, {\n source: 'elysia',\n named: ['Elysia'],\n });\n\n let registered = false;\n recast.visit(ast, {\n visitNewExpression(path) {\n if (registered) return false;\n if (isFactoryCall(path.node, factoryNames, 'NewExpression')) {\n const withPlugin = parseStatement(\n '__app__.use(intlayer());'\n ).expression;\n withPlugin.callee.object = path.node;\n path.replace(withPlugin);\n registered = true;\n return false;\n }\n this.traverse(path);\n },\n });\n\n if (!registered) return { code, status: 'skipped' };\n\n ensureMiddlewareBinding(ast, middlewareSource);\n\n return { code: recast.print(ast).code, status: 'registered' };\n};\n"],"mappings":"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"}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
import { backendAdapters } from "./backend/index.mjs";
|
|
1
2
|
import { nextAppRouterAdapter } from "./nextAppRouter/index.mjs";
|
|
2
3
|
import { tanStackStartAdapter } from "./tanstackStart/index.mjs";
|
|
3
4
|
|
|
4
5
|
//#region src/init/frameworkSetup/index.ts
|
|
5
6
|
/**
|
|
6
7
|
* Registered framework adapters, tried in order. The first one whose `detect`
|
|
7
|
-
* returns true handles the project.
|
|
8
|
-
*
|
|
8
|
+
* returns true handles the project. Frontend adapters come first, so an app
|
|
9
|
+
* with its own server keeps its frontend scaffolding. Add new adapters (Nuxt,
|
|
10
|
+
* Vite + React, …) here as they are implemented.
|
|
9
11
|
*/
|
|
10
|
-
const adapters = [
|
|
12
|
+
const adapters = [
|
|
13
|
+
nextAppRouterAdapter,
|
|
14
|
+
tanStackStartAdapter,
|
|
15
|
+
...backendAdapters
|
|
16
|
+
];
|
|
11
17
|
/**
|
|
12
18
|
* Runs framework-specific scaffolding (middleware/proxy, providers in
|
|
13
19
|
* layout/page, example content) for the first adapter that matches the project.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/init/frameworkSetup/index.ts"],"sourcesContent":["import { nextAppRouterAdapter } from './nextAppRouter';\nimport { tanStackStartAdapter } from './tanstackStart';\nimport type { FrameworkAdapter, FrameworkSetupContext } from './types';\n\nexport type { FrameworkAdapter, FrameworkSetupContext } from './types';\n\n/**\n * Registered framework adapters, tried in order. The first one whose `detect`\n * returns true handles the project. Add new adapters (Nuxt
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/init/frameworkSetup/index.ts"],"sourcesContent":["import { backendAdapters } from './backend';\nimport { nextAppRouterAdapter } from './nextAppRouter';\nimport { tanStackStartAdapter } from './tanstackStart';\nimport type { FrameworkAdapter, FrameworkSetupContext } from './types';\n\nexport type { FrameworkAdapter, FrameworkSetupContext } from './types';\n\n/**\n * Registered framework adapters, tried in order. The first one whose `detect`\n * returns true handles the project. Frontend adapters come first, so an app\n * with its own server keeps its frontend scaffolding. Add new adapters (Nuxt,\n * Vite + React, …) here as they are implemented.\n */\nconst adapters: FrameworkAdapter[] = [\n nextAppRouterAdapter,\n tanStackStartAdapter,\n ...backendAdapters,\n];\n\n/**\n * Runs framework-specific scaffolding (middleware/proxy, providers in\n * layout/page, example content) for the first adapter that matches the project.\n * No-ops when no adapter recognizes the project. Each adapter is idempotent and\n * non-destructive, so this is safe to run on existing applications.\n */\nexport const setupFramework = async (\n context: FrameworkSetupContext\n): Promise<void> => {\n for (const adapter of adapters) {\n if (await adapter.detect(context)) {\n await adapter.setup(context);\n return;\n }\n }\n};\n"],"mappings":";;;;;;;;;;;AAaA,MAAM,WAA+B;CACnC;CACA;CACA,GAAG;AACL;;;;;;;AAQA,MAAa,iBAAiB,OAC5B,YACkB;CAClB,KAAK,MAAM,WAAW,UACpB,IAAI,MAAM,QAAQ,OAAO,OAAO,GAAG;EACjC,MAAM,QAAQ,MAAM,OAAO;EAC3B;CACF;AAEJ"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/init/frameworkSetup/localeRouting.ts
|
|
2
|
+
/**
|
|
3
|
+
* Whether the locale is carried by a URL path segment (`/fr/about`), which the
|
|
4
|
+
* framework router must declare (`[locale]`, `{-$locale}`, `$locale`…). Only the
|
|
5
|
+
* prefix routing modes do, and only while the proxy is enabled: `no-prefix` and
|
|
6
|
+
* `search-params` resolve the locale from storage/headers/query instead.
|
|
7
|
+
*/
|
|
8
|
+
const usesLocalePathSegment = ({ routingMode, enableProxy }) => enableProxy && (routingMode === "prefix-all" || routingMode === "prefix-no-default");
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
export { usesLocalePathSegment };
|
|
12
|
+
//# sourceMappingURL=localeRouting.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localeRouting.mjs","names":[],"sources":["../../../../src/init/frameworkSetup/localeRouting.ts"],"sourcesContent":["import type { FrameworkSetupContext } from './types';\n\n/**\n * Whether the locale is carried by a URL path segment (`/fr/about`), which the\n * framework router must declare (`[locale]`, `{-$locale}`, `$locale`…). Only the\n * prefix routing modes do, and only while the proxy is enabled: `no-prefix` and\n * `search-params` resolve the locale from storage/headers/query instead.\n */\nexport const usesLocalePathSegment = ({\n routingMode,\n enableProxy,\n}: Pick<FrameworkSetupContext, 'routingMode' | 'enableProxy'>): boolean =>\n enableProxy &&\n (routingMode === 'prefix-all' || routingMode === 'prefix-no-default');\n"],"mappings":";;;;;;;AAQA,MAAa,yBAAyB,EACpC,aACA,kBAEA,gBACC,gBAAgB,gBAAgB,gBAAgB"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { ensureDirectory, exists, readFileFromRoot, writeFileToRoot } from "../../utils/fileSystem.mjs";
|
|
2
|
+
import { usesLocalePathSegment } from "../localeRouting.mjs";
|
|
2
3
|
import { isVersionAtLeast } from "../../utils/version.mjs";
|
|
3
4
|
import { detectNextAppDir, findAppFile } from "./detect.mjs";
|
|
4
|
-
import { restructureAppIntoLocale } from "./restructure.mjs";
|
|
5
|
-
import { LOCALE_LAYOUT_TEMPLATE_JS, LOCALE_LAYOUT_TEMPLATE_TS, LOCALE_PAGE_TEMPLATE_JS, LOCALE_PAGE_TEMPLATE_TS, MIDDLEWARE_TEMPLATE, PROXY_TEMPLATE, ROOT_LAYOUT_TEMPLATE_JS, ROOT_LAYOUT_TEMPLATE_TS } from "./templates.mjs";
|
|
5
|
+
import { isLocaleSegment, restructureAppIntoLocale } from "./restructure.mjs";
|
|
6
|
+
import { LOCALE_LAYOUT_TEMPLATE_JS, LOCALE_LAYOUT_TEMPLATE_TS, LOCALE_PAGE_TEMPLATE_JS, LOCALE_PAGE_TEMPLATE_TS, MIDDLEWARE_TEMPLATE, PROXY_TEMPLATE, ROOT_LAYOUT_TEMPLATE_JS, ROOT_LAYOUT_TEMPLATE_TS, UNPREFIXED_PAGE_TEMPLATE, UNPREFIXED_ROOT_LAYOUT_TEMPLATE_JS, UNPREFIXED_ROOT_LAYOUT_TEMPLATE_TS } from "./templates.mjs";
|
|
6
7
|
import { wrapLayoutWithProvider } from "./transforms.mjs";
|
|
7
8
|
import { join } from "node:path";
|
|
8
9
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
9
10
|
import { colorize, colorizePath, logger, v, x } from "@intlayer/config/logger";
|
|
11
|
+
import { readdir } from "node:fs/promises";
|
|
10
12
|
|
|
11
13
|
//#region src/init/frameworkSetup/nextAppRouter/index.ts
|
|
12
14
|
/**
|
|
@@ -61,11 +63,29 @@ const createIfMissing = async (rootDir, filePath, content, description) => {
|
|
|
61
63
|
return true;
|
|
62
64
|
};
|
|
63
65
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
66
|
+
* Wires an app whose URLs carry no locale segment (`no-prefix`,
|
|
67
|
+
* `search-params`, or no proxy): routes stay at the app root and the root
|
|
68
|
+
* layout provides the locale resolved by `getLocale()`. An app already nested
|
|
69
|
+
* under a locale segment is left untouched, with guidance.
|
|
70
|
+
*/
|
|
71
|
+
const setupUnprefixedApp = async (rootDir, appDir, useTypeScript) => {
|
|
72
|
+
const scriptExtension = useTypeScript ? "tsx" : "jsx";
|
|
73
|
+
const existingLocaleSegment = (await readdir(join(rootDir, appDir))).find(isLocaleSegment);
|
|
74
|
+
if (existingLocaleSegment) {
|
|
75
|
+
logger(`${x} Routes live under ${colorizePath(join(appDir, existingLocaleSegment))} but the chosen routing has no locale in the URL — move them back to ${colorizePath(appDir)} manually.`, { level: "warn" });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const existingLayout = await findAppFile(rootDir, appDir, "layout");
|
|
79
|
+
if (existingLayout) await transformExistingFile(rootDir, existingLayout, (code) => wrapLayoutWithProvider(code, { exportStaticParams: false }));
|
|
80
|
+
else await createIfMissing(rootDir, join(appDir, `layout.${scriptExtension}`), useTypeScript ? UNPREFIXED_ROOT_LAYOUT_TEMPLATE_TS : UNPREFIXED_ROOT_LAYOUT_TEMPLATE_JS, "root layout");
|
|
81
|
+
if (!await findAppFile(rootDir, appDir, "page")) await createIfMissing(rootDir, join(appDir, `page.${scriptExtension}`), UNPREFIXED_PAGE_TEMPLATE, "page");
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Next.js App Router adapter. Scaffolds the locale proxy/middleware (when the
|
|
85
|
+
* proxy is enabled), restructures the app into a `[locale]` segment (prefix
|
|
86
|
+
* routing modes only), and wraps the layout with the unified Intlayer provider
|
|
87
|
+
* — all idempotently and without overwriting recognizable user code. Pages are
|
|
88
|
+
* never wrapped: `IntlayerProvider` in the layout covers them.
|
|
69
89
|
*/
|
|
70
90
|
const nextAppRouterAdapter = {
|
|
71
91
|
name: "Next.js App Router",
|
|
@@ -73,14 +93,21 @@ const nextAppRouterAdapter = {
|
|
|
73
93
|
if (!allDeps.next) return false;
|
|
74
94
|
return await detectNextAppDir(rootDir) !== null;
|
|
75
95
|
},
|
|
76
|
-
setup: async ({ rootDir, allDeps, useTypeScript }) => {
|
|
96
|
+
setup: async ({ rootDir, allDeps, useTypeScript, routingMode, enableProxy }) => {
|
|
77
97
|
const appDirInfo = await detectNextAppDir(rootDir);
|
|
78
98
|
if (!appDirInfo) return;
|
|
79
99
|
const { appDir, srcDir } = appDirInfo;
|
|
80
100
|
const isNext16 = isVersionAtLeast(allDeps.next, 16);
|
|
81
101
|
const scriptExtension = useTypeScript ? "tsx" : "jsx";
|
|
82
102
|
logger(colorize("Setting up Next.js App Router integration...", ANSIColors.CYAN));
|
|
83
|
-
await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);
|
|
103
|
+
if (enableProxy) await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);
|
|
104
|
+
if (!usesLocalePathSegment({
|
|
105
|
+
routingMode,
|
|
106
|
+
enableProxy
|
|
107
|
+
})) {
|
|
108
|
+
await setupUnprefixedApp(rootDir, appDir, useTypeScript);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
84
111
|
const restructureResult = await restructureAppIntoLocale(rootDir, appDir);
|
|
85
112
|
if (restructureResult.status === "moved") logger(`${v} Restructured app routes under ${colorizePath(join(appDir, "[locale]"))}: ${restructureResult.movedEntries.map((entry) => colorize(entry, ANSIColors.MAGENTA)).join(", ")}`);
|
|
86
113
|
else if (restructureResult.status === "already-structured") logger(`${v} ${colorizePath(join(appDir, restructureResult.localeSegment))} already exists, skipping restructure`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport {\n ensureDirectory,\n exists,\n readFileFromRoot,\n writeFileToRoot,\n} from '../../utils/fileSystem';\nimport type { FrameworkAdapter, FrameworkSetupContext } from '../types';\nimport { detectNextAppDir, findAppFile, isVersionAtLeast } from './detect';\nimport { restructureAppIntoLocale } from './restructure';\nimport {\n LOCALE_LAYOUT_TEMPLATE_JS,\n LOCALE_LAYOUT_TEMPLATE_TS,\n LOCALE_PAGE_TEMPLATE_JS,\n LOCALE_PAGE_TEMPLATE_TS,\n MIDDLEWARE_TEMPLATE,\n PROXY_TEMPLATE,\n ROOT_LAYOUT_TEMPLATE_JS,\n ROOT_LAYOUT_TEMPLATE_TS,\n} from './templates';\nimport { type TransformResult, wrapLayoutWithProvider } from './transforms';\n\n/**\n * Creates the locale-detection proxy (Next >= 16) or middleware (Next < 16),\n * but only when neither already exists, so a user's custom file is never\n * touched. Placed next to the app directory's parent (`src/` when present).\n */\nconst ensureProxyOrMiddleware = async (\n rootDir: string,\n srcDir: string,\n isNext16: boolean\n): Promise<void> => {\n const baseDir = srcDir || '.';\n const proxyCandidates = [\n 'proxy.ts',\n 'proxy.js',\n 'middleware.ts',\n 'middleware.js',\n ];\n\n for (const candidate of proxyCandidates) {\n if (await exists(rootDir, join(baseDir, candidate))) {\n logger(\n `${v} ${colorizePath(join(baseDir, candidate))} already exists, leaving it untouched`\n );\n return;\n }\n }\n\n const fileName = isNext16 ? 'proxy.ts' : 'middleware.ts';\n const content = isNext16 ? PROXY_TEMPLATE : MIDDLEWARE_TEMPLATE;\n const targetPath = join(baseDir, fileName);\n\n if (srcDir) await ensureDirectory(rootDir, srcDir);\n await writeFileToRoot(rootDir, targetPath, content);\n logger(`${v} Created ${colorizePath(targetPath)} for locale detection`);\n};\n\n/** Logs the outcome of a layout provider-wrap transform. */\nconst logTransformOutcome = (\n filePath: string,\n result: TransformResult\n): void => {\n switch (result.status) {\n case 'wrapped':\n logger(`${v} Added Intlayer provider to ${colorizePath(filePath)}`);\n break;\n case 'already':\n logger(\n `${v} ${colorizePath(filePath)} already wraps the Intlayer provider`\n );\n break;\n case 'skipped-client':\n logger(\n `${x} ${colorizePath(filePath)} is a client component — add the Intlayer provider manually.`,\n { level: 'warn' }\n );\n break;\n case 'skipped':\n logger(\n `${x} Could not safely add the Intlayer provider to ${colorizePath(filePath)} — please wire it manually.`,\n { level: 'warn' }\n );\n break;\n }\n};\n\n/** Transforms an existing file in place, writing it back only when changed. */\nconst transformExistingFile = async (\n rootDir: string,\n filePath: string,\n transform: (code: string) => TransformResult\n): Promise<void> => {\n const code = await readFileFromRoot(rootDir, filePath);\n const result = transform(code);\n if (result.status === 'wrapped') {\n await writeFileToRoot(rootDir, filePath, result.code);\n }\n logTransformOutcome(filePath, result);\n};\n\n/** Creates a file from a template only when it does not already exist. */\nconst createIfMissing = async (\n rootDir: string,\n filePath: string,\n content: string,\n description: string\n): Promise<boolean> => {\n if (await exists(rootDir, filePath)) return false;\n await writeFileToRoot(rootDir, filePath, content);\n logger(`${v} Created ${description} ${colorizePath(filePath)}`);\n return true;\n};\n\n/**\n * Next.js App Router adapter. Scaffolds the locale proxy/middleware, restructures\n * the app into a `[locale]` segment, and wraps the layout with the unified\n * Intlayer provider — all idempotently and without overwriting recognizable\n * user code. Pages are never wrapped: `IntlayerProvider` in the locale layout\n * covers them.\n */\nexport const nextAppRouterAdapter: FrameworkAdapter = {\n name: 'Next.js App Router',\n\n detect: async ({ rootDir, allDeps }) => {\n if (!allDeps.next) return false;\n return (await detectNextAppDir(rootDir)) !== null;\n },\n\n setup: async ({ rootDir, allDeps, useTypeScript }: FrameworkSetupContext) => {\n const appDirInfo = await detectNextAppDir(rootDir);\n if (!appDirInfo) return;\n\n const { appDir, srcDir } = appDirInfo;\n const isNext16 = isVersionAtLeast(allDeps.next, 16);\n const scriptExtension = useTypeScript ? 'tsx' : 'jsx';\n\n logger(\n colorize('Setting up Next.js App Router integration...', ANSIColors.CYAN)\n );\n\n // 1. Locale detection proxy / middleware (create only when absent).\n await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);\n\n // 2. Move routable files under a `[locale]` segment (idempotent).\n const restructureResult = await restructureAppIntoLocale(rootDir, appDir);\n if (restructureResult.status === 'moved') {\n logger(\n `${v} Restructured app routes under ${colorizePath(join(appDir, '[locale]'))}: ${restructureResult.movedEntries\n .map((entry) => colorize(entry, ANSIColors.MAGENTA))\n .join(', ')}`\n );\n } else if (restructureResult.status === 'already-structured') {\n logger(\n `${v} ${colorizePath(join(appDir, restructureResult.localeSegment))} already exists, skipping restructure`\n );\n }\n\n // When the app already uses a different locale-prefix scheme (e.g. an\n // `[...locale]` / `[[...locale]]` catch-all), respect the user's routing:\n // don't scaffold a second, conflicting `[locale]` segment.\n if (\n restructureResult.status === 'already-structured' &&\n restructureResult.localeSegment !== '[locale]'\n ) {\n logger(\n `${v} Detected existing locale routing (${colorize(restructureResult.localeSegment, ANSIColors.MAGENTA)}), skipping locale layout/page scaffold`\n );\n return;\n }\n\n const localeDir = join(appDir, '[locale]');\n await ensureDirectory(rootDir, localeDir);\n\n // 3. Minimal pass-through root layout (the `[locale]` layout owns `<html>`).\n await createIfMissing(\n rootDir,\n join(appDir, `layout.${scriptExtension}`),\n useTypeScript ? ROOT_LAYOUT_TEMPLATE_TS : ROOT_LAYOUT_TEMPLATE_JS,\n 'root layout'\n );\n\n // 4. Locale layout — transform an existing one, else scaffold from template.\n const existingLayout = await findAppFile(rootDir, localeDir, 'layout');\n if (existingLayout) {\n await transformExistingFile(\n rootDir,\n existingLayout,\n wrapLayoutWithProvider\n );\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `layout.${scriptExtension}`),\n useTypeScript ? LOCALE_LAYOUT_TEMPLATE_TS : LOCALE_LAYOUT_TEMPLATE_JS,\n 'locale layout'\n );\n }\n\n // 5. Locale page — only scaffolded when absent. An existing page needs no\n // transform: the locale layout's `IntlayerProvider` already covers it.\n const existingPage = await findAppFile(rootDir, localeDir, 'page');\n if (!existingPage) {\n await createIfMissing(\n rootDir,\n join(localeDir, `page.${scriptExtension}`),\n useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS,\n 'locale page'\n );\n }\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/index.ts"],"sourcesContent":["import { readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport {\n ensureDirectory,\n exists,\n readFileFromRoot,\n writeFileToRoot,\n} from '../../utils/fileSystem';\nimport { usesLocalePathSegment } from '../localeRouting';\nimport type { FrameworkAdapter, FrameworkSetupContext } from '../types';\nimport { detectNextAppDir, findAppFile, isVersionAtLeast } from './detect';\nimport { isLocaleSegment, restructureAppIntoLocale } from './restructure';\nimport {\n LOCALE_LAYOUT_TEMPLATE_JS,\n LOCALE_LAYOUT_TEMPLATE_TS,\n LOCALE_PAGE_TEMPLATE_JS,\n LOCALE_PAGE_TEMPLATE_TS,\n MIDDLEWARE_TEMPLATE,\n PROXY_TEMPLATE,\n ROOT_LAYOUT_TEMPLATE_JS,\n ROOT_LAYOUT_TEMPLATE_TS,\n UNPREFIXED_PAGE_TEMPLATE,\n UNPREFIXED_ROOT_LAYOUT_TEMPLATE_JS,\n UNPREFIXED_ROOT_LAYOUT_TEMPLATE_TS,\n} from './templates';\nimport { type TransformResult, wrapLayoutWithProvider } from './transforms';\n\n/**\n * Creates the locale-detection proxy (Next >= 16) or middleware (Next < 16),\n * but only when neither already exists, so a user's custom file is never\n * touched. Placed next to the app directory's parent (`src/` when present).\n */\nconst ensureProxyOrMiddleware = async (\n rootDir: string,\n srcDir: string,\n isNext16: boolean\n): Promise<void> => {\n const baseDir = srcDir || '.';\n const proxyCandidates = [\n 'proxy.ts',\n 'proxy.js',\n 'middleware.ts',\n 'middleware.js',\n ];\n\n for (const candidate of proxyCandidates) {\n if (await exists(rootDir, join(baseDir, candidate))) {\n logger(\n `${v} ${colorizePath(join(baseDir, candidate))} already exists, leaving it untouched`\n );\n return;\n }\n }\n\n const fileName = isNext16 ? 'proxy.ts' : 'middleware.ts';\n const content = isNext16 ? PROXY_TEMPLATE : MIDDLEWARE_TEMPLATE;\n const targetPath = join(baseDir, fileName);\n\n if (srcDir) await ensureDirectory(rootDir, srcDir);\n await writeFileToRoot(rootDir, targetPath, content);\n logger(`${v} Created ${colorizePath(targetPath)} for locale detection`);\n};\n\n/** Logs the outcome of a layout provider-wrap transform. */\nconst logTransformOutcome = (\n filePath: string,\n result: TransformResult\n): void => {\n switch (result.status) {\n case 'wrapped':\n logger(`${v} Added Intlayer provider to ${colorizePath(filePath)}`);\n break;\n case 'already':\n logger(\n `${v} ${colorizePath(filePath)} already wraps the Intlayer provider`\n );\n break;\n case 'skipped-client':\n logger(\n `${x} ${colorizePath(filePath)} is a client component — add the Intlayer provider manually.`,\n { level: 'warn' }\n );\n break;\n case 'skipped':\n logger(\n `${x} Could not safely add the Intlayer provider to ${colorizePath(filePath)} — please wire it manually.`,\n { level: 'warn' }\n );\n break;\n }\n};\n\n/** Transforms an existing file in place, writing it back only when changed. */\nconst transformExistingFile = async (\n rootDir: string,\n filePath: string,\n transform: (code: string) => TransformResult\n): Promise<void> => {\n const code = await readFileFromRoot(rootDir, filePath);\n const result = transform(code);\n if (result.status === 'wrapped') {\n await writeFileToRoot(rootDir, filePath, result.code);\n }\n logTransformOutcome(filePath, result);\n};\n\n/** Creates a file from a template only when it does not already exist. */\nconst createIfMissing = async (\n rootDir: string,\n filePath: string,\n content: string,\n description: string\n): Promise<boolean> => {\n if (await exists(rootDir, filePath)) return false;\n await writeFileToRoot(rootDir, filePath, content);\n logger(`${v} Created ${description} ${colorizePath(filePath)}`);\n return true;\n};\n\n/**\n * Wires an app whose URLs carry no locale segment (`no-prefix`,\n * `search-params`, or no proxy): routes stay at the app root and the root\n * layout provides the locale resolved by `getLocale()`. An app already nested\n * under a locale segment is left untouched, with guidance.\n */\nconst setupUnprefixedApp = async (\n rootDir: string,\n appDir: string,\n useTypeScript: boolean\n): Promise<void> => {\n const scriptExtension = useTypeScript ? 'tsx' : 'jsx';\n\n const entries = await readdir(join(rootDir, appDir));\n const existingLocaleSegment = entries.find(isLocaleSegment);\n if (existingLocaleSegment) {\n logger(\n `${x} Routes live under ${colorizePath(join(appDir, existingLocaleSegment))} but the chosen routing has no locale in the URL — move them back to ${colorizePath(appDir)} manually.`,\n { level: 'warn' }\n );\n return;\n }\n\n const existingLayout = await findAppFile(rootDir, appDir, 'layout');\n if (existingLayout) {\n await transformExistingFile(rootDir, existingLayout, (code) =>\n wrapLayoutWithProvider(code, { exportStaticParams: false })\n );\n } else {\n await createIfMissing(\n rootDir,\n join(appDir, `layout.${scriptExtension}`),\n useTypeScript\n ? UNPREFIXED_ROOT_LAYOUT_TEMPLATE_TS\n : UNPREFIXED_ROOT_LAYOUT_TEMPLATE_JS,\n 'root layout'\n );\n }\n\n if (!(await findAppFile(rootDir, appDir, 'page'))) {\n await createIfMissing(\n rootDir,\n join(appDir, `page.${scriptExtension}`),\n UNPREFIXED_PAGE_TEMPLATE,\n 'page'\n );\n }\n};\n\n/**\n * Next.js App Router adapter. Scaffolds the locale proxy/middleware (when the\n * proxy is enabled), restructures the app into a `[locale]` segment (prefix\n * routing modes only), and wraps the layout with the unified Intlayer provider\n * — all idempotently and without overwriting recognizable user code. Pages are\n * never wrapped: `IntlayerProvider` in the layout covers them.\n */\nexport const nextAppRouterAdapter: FrameworkAdapter = {\n name: 'Next.js App Router',\n\n detect: async ({ rootDir, allDeps }) => {\n if (!allDeps.next) return false;\n return (await detectNextAppDir(rootDir)) !== null;\n },\n\n setup: async ({\n rootDir,\n allDeps,\n useTypeScript,\n routingMode,\n enableProxy,\n }: FrameworkSetupContext) => {\n const appDirInfo = await detectNextAppDir(rootDir);\n if (!appDirInfo) return;\n\n const { appDir, srcDir } = appDirInfo;\n const isNext16 = isVersionAtLeast(allDeps.next, 16);\n const scriptExtension = useTypeScript ? 'tsx' : 'jsx';\n\n logger(\n colorize('Setting up Next.js App Router integration...', ANSIColors.CYAN)\n );\n\n // 1. Locale detection proxy / middleware (create only when absent).\n if (enableProxy) {\n await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);\n }\n\n // Without a locale path segment, routes stay where they are.\n if (!usesLocalePathSegment({ routingMode, enableProxy })) {\n await setupUnprefixedApp(rootDir, appDir, useTypeScript);\n return;\n }\n\n // 2. Move routable files under a `[locale]` segment (idempotent).\n const restructureResult = await restructureAppIntoLocale(rootDir, appDir);\n if (restructureResult.status === 'moved') {\n logger(\n `${v} Restructured app routes under ${colorizePath(join(appDir, '[locale]'))}: ${restructureResult.movedEntries\n .map((entry) => colorize(entry, ANSIColors.MAGENTA))\n .join(', ')}`\n );\n } else if (restructureResult.status === 'already-structured') {\n logger(\n `${v} ${colorizePath(join(appDir, restructureResult.localeSegment))} already exists, skipping restructure`\n );\n }\n\n // When the app already uses a different locale-prefix scheme (e.g. an\n // `[...locale]` / `[[...locale]]` catch-all), respect the user's routing:\n // don't scaffold a second, conflicting `[locale]` segment.\n if (\n restructureResult.status === 'already-structured' &&\n restructureResult.localeSegment !== '[locale]'\n ) {\n logger(\n `${v} Detected existing locale routing (${colorize(restructureResult.localeSegment, ANSIColors.MAGENTA)}), skipping locale layout/page scaffold`\n );\n return;\n }\n\n const localeDir = join(appDir, '[locale]');\n await ensureDirectory(rootDir, localeDir);\n\n // 3. Minimal pass-through root layout (the `[locale]` layout owns `<html>`).\n await createIfMissing(\n rootDir,\n join(appDir, `layout.${scriptExtension}`),\n useTypeScript ? ROOT_LAYOUT_TEMPLATE_TS : ROOT_LAYOUT_TEMPLATE_JS,\n 'root layout'\n );\n\n // 4. Locale layout — transform an existing one, else scaffold from template.\n const existingLayout = await findAppFile(rootDir, localeDir, 'layout');\n if (existingLayout) {\n await transformExistingFile(\n rootDir,\n existingLayout,\n wrapLayoutWithProvider\n );\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `layout.${scriptExtension}`),\n useTypeScript ? LOCALE_LAYOUT_TEMPLATE_TS : LOCALE_LAYOUT_TEMPLATE_JS,\n 'locale layout'\n );\n }\n\n // 5. Locale page — only scaffolded when absent. An existing page needs no\n // transform: the locale layout's `IntlayerProvider` already covers it.\n const existingPage = await findAppFile(rootDir, localeDir, 'page');\n if (!existingPage) {\n await createIfMissing(\n rootDir,\n join(localeDir, `page.${scriptExtension}`),\n useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS,\n 'locale page'\n );\n }\n },\n};\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { babelTsParser } from "../../../utils/babelParser.mjs";
|
|
2
|
-
import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
3
2
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
3
|
import fg from "fast-glob";
|
|
4
|
+
import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
5
5
|
import * as recast from "recast";
|
|
6
6
|
|
|
7
7
|
//#region src/init/frameworkSetup/nextAppRouter/restructure.ts
|
|
@@ -107,9 +107,62 @@ const LOCALE_PAGE_TEMPLATE_JS = `const Page = async ({ params }) => {
|
|
|
107
107
|
);
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
+
export default Page;
|
|
111
|
+
`;
|
|
112
|
+
/**
|
|
113
|
+
* Root layout (`app/layout.tsx`) for routing modes without a locale path
|
|
114
|
+
* segment (`no-prefix`, `search-params`, or no proxy at all). Owns `<html>` and
|
|
115
|
+
* resolves the locale from the request (cookie / header) via `getLocale()`.
|
|
116
|
+
*/
|
|
117
|
+
const UNPREFIXED_ROOT_LAYOUT_TEMPLATE_TS = `import type { PropsWithChildren } from "react";
|
|
118
|
+
import { getHTMLTextDir } from "intlayer";
|
|
119
|
+
import { getLocale, IntlayerProvider } from "next-intlayer/server";
|
|
120
|
+
|
|
121
|
+
const RootLayout = async ({ children }: PropsWithChildren) => {
|
|
122
|
+
const locale = await getLocale();
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<html lang={locale} dir={getHTMLTextDir(locale)}>
|
|
126
|
+
<body>
|
|
127
|
+
<IntlayerProvider locale={locale}>{children}</IntlayerProvider>
|
|
128
|
+
</body>
|
|
129
|
+
</html>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export default RootLayout;
|
|
134
|
+
`;
|
|
135
|
+
const UNPREFIXED_ROOT_LAYOUT_TEMPLATE_JS = `import { getHTMLTextDir } from "intlayer";
|
|
136
|
+
import { getLocale, IntlayerProvider } from "next-intlayer/server";
|
|
137
|
+
|
|
138
|
+
const RootLayout = async ({ children }) => {
|
|
139
|
+
const locale = await getLocale();
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<html lang={locale} dir={getHTMLTextDir(locale)}>
|
|
143
|
+
<body>
|
|
144
|
+
<IntlayerProvider locale={locale}>{children}</IntlayerProvider>
|
|
145
|
+
</body>
|
|
146
|
+
</html>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export default RootLayout;
|
|
151
|
+
`;
|
|
152
|
+
/**
|
|
153
|
+
* Home page (`app/page.tsx`) for routing modes without a locale path segment.
|
|
154
|
+
* Identical for TypeScript and JavaScript projects.
|
|
155
|
+
*/
|
|
156
|
+
const UNPREFIXED_PAGE_TEMPLATE = `const Page = () => (
|
|
157
|
+
<main>
|
|
158
|
+
<h1>Hello</h1>
|
|
159
|
+
<p>Get started by editing this page.</p>
|
|
160
|
+
</main>
|
|
161
|
+
);
|
|
162
|
+
|
|
110
163
|
export default Page;
|
|
111
164
|
`;
|
|
112
165
|
|
|
113
166
|
//#endregion
|
|
114
|
-
export { LOCALE_LAYOUT_TEMPLATE_JS, LOCALE_LAYOUT_TEMPLATE_TS, LOCALE_PAGE_TEMPLATE_JS, LOCALE_PAGE_TEMPLATE_TS, MIDDLEWARE_TEMPLATE, PROXY_TEMPLATE, ROOT_LAYOUT_TEMPLATE_JS, ROOT_LAYOUT_TEMPLATE_TS };
|
|
167
|
+
export { LOCALE_LAYOUT_TEMPLATE_JS, LOCALE_LAYOUT_TEMPLATE_TS, LOCALE_PAGE_TEMPLATE_JS, LOCALE_PAGE_TEMPLATE_TS, MIDDLEWARE_TEMPLATE, PROXY_TEMPLATE, ROOT_LAYOUT_TEMPLATE_JS, ROOT_LAYOUT_TEMPLATE_TS, UNPREFIXED_PAGE_TEMPLATE, UNPREFIXED_ROOT_LAYOUT_TEMPLATE_JS, UNPREFIXED_ROOT_LAYOUT_TEMPLATE_TS };
|
|
115
168
|
//# sourceMappingURL=templates.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/templates.ts"],"sourcesContent":["/**\n * File templates for a fresh Next.js App Router integration. These are only\n * written when the target file does not exist (or, for the locale layout/page,\n * when the app is effectively empty) — they never overwrite user code.\n */\n\n/** Next.js >= 16 locale-detection proxy (`src/proxy.ts`). */\nexport const PROXY_TEMPLATE = `export { intlayerProxy as proxy } from \"next-intlayer/proxy\";\n\nexport const config = {\n matcher:\n \"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\\\\\\\..*|_next).*)\",\n};\n`;\n\n/** Next.js < 16 locale-detection middleware (`src/middleware.ts`). */\nexport const MIDDLEWARE_TEMPLATE = `export { intlayerMiddleware as middleware } from \"next-intlayer/middleware\";\n\nexport const config = {\n matcher:\n \"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\\\\\\\..*|_next).*)\",\n};\n`;\n\n/** Minimal pass-through root layout (`app/layout.tsx`), lets `[locale]/layout` own `<html>`. */\nexport const ROOT_LAYOUT_TEMPLATE_TS = `import type { PropsWithChildren, FC } from \"react\";\n\nconst RootLayout: FC<PropsWithChildren> = ({ children }) => <>{children}</>;\n\nexport default RootLayout;\n`;\n\nexport const ROOT_LAYOUT_TEMPLATE_JS = `const RootLayout = ({ children }) => <>{children}</>;\n\nexport default RootLayout;\n`;\n\n/**\n * Locale layout (`app/[locale]/layout.tsx`) providing `<html>` + the unified\n * provider. `IntlayerProvider` seeds both the server context (read by server\n * hooks and by any page below, without per-page wrapping) and the client\n * provider in one mount.\n */\nexport const LOCALE_LAYOUT_TEMPLATE_TS = `import { type NextLayoutIntlayer } from \"next-intlayer\";\nimport { IntlayerProvider } from \"next-intlayer/server\";\nimport { getHTMLTextDir } from \"intlayer\";\n\nexport { generateStaticParams } from \"next-intlayer\";\n\nconst LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {\n const { locale } = await params;\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerProvider locale={locale}>{children}</IntlayerProvider>\n </body>\n </html>\n );\n};\n\nexport default LocaleLayout;\n`;\n\nexport const LOCALE_LAYOUT_TEMPLATE_JS = `import { IntlayerProvider } from \"next-intlayer/server\";\nimport { getHTMLTextDir } from \"intlayer\";\n\nexport { generateStaticParams } from \"next-intlayer\";\n\nconst LocaleLayout = async ({ children, params }) => {\n const { locale } = await params;\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerProvider locale={locale}>{children}</IntlayerProvider>\n </body>\n </html>\n );\n};\n\nexport default LocaleLayout;\n`;\n\n/**\n * Locale home page (`app/[locale]/page.tsx`). Self-contained starter that does\n * not depend on an example content declaration — add Intlayer content (a\n * `*.content.ts` file consumed via `useIntlayer`) as the next step.\n */\nexport const LOCALE_PAGE_TEMPLATE_TS = `import { type NextPageIntlayer } from \"next-intlayer\";\n\nconst Page: NextPageIntlayer = async ({ params }) => {\n const { locale } = await params;\n\n return (\n <main>\n <h1>{locale}</h1>\n <p>Get started by editing this page.</p>\n </main>\n );\n};\n\nexport default Page;\n`;\n\nexport const LOCALE_PAGE_TEMPLATE_JS = `const Page = async ({ params }) => {\n const { locale } = await params;\n\n return (\n <main>\n <h1>{locale}</h1>\n <p>Get started by editing this page.</p>\n </main>\n );\n};\n\nexport default Page;\n`;\n"],"mappings":";;;;;;;AAOA,MAAa,iBAAiB;;;;;;;;AAS9B,MAAa,sBAAsB;;;;;;;;AASnC,MAAa,0BAA0B;;;;;;AAOvC,MAAa,0BAA0B;;;;;;;;;;AAWvC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;AAqBzC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;AAyBzC,MAAa,0BAA0B;;;;;;;;;;;;;;;AAgBvC,MAAa,0BAA0B"}
|
|
1
|
+
{"version":3,"file":"templates.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/templates.ts"],"sourcesContent":["/**\n * File templates for a fresh Next.js App Router integration. These are only\n * written when the target file does not exist (or, for the locale layout/page,\n * when the app is effectively empty) — they never overwrite user code.\n */\n\n/** Next.js >= 16 locale-detection proxy (`src/proxy.ts`). */\nexport const PROXY_TEMPLATE = `export { intlayerProxy as proxy } from \"next-intlayer/proxy\";\n\nexport const config = {\n matcher:\n \"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\\\\\\\..*|_next).*)\",\n};\n`;\n\n/** Next.js < 16 locale-detection middleware (`src/middleware.ts`). */\nexport const MIDDLEWARE_TEMPLATE = `export { intlayerMiddleware as middleware } from \"next-intlayer/middleware\";\n\nexport const config = {\n matcher:\n \"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\\\\\\\..*|_next).*)\",\n};\n`;\n\n/** Minimal pass-through root layout (`app/layout.tsx`), lets `[locale]/layout` own `<html>`. */\nexport const ROOT_LAYOUT_TEMPLATE_TS = `import type { PropsWithChildren, FC } from \"react\";\n\nconst RootLayout: FC<PropsWithChildren> = ({ children }) => <>{children}</>;\n\nexport default RootLayout;\n`;\n\nexport const ROOT_LAYOUT_TEMPLATE_JS = `const RootLayout = ({ children }) => <>{children}</>;\n\nexport default RootLayout;\n`;\n\n/**\n * Locale layout (`app/[locale]/layout.tsx`) providing `<html>` + the unified\n * provider. `IntlayerProvider` seeds both the server context (read by server\n * hooks and by any page below, without per-page wrapping) and the client\n * provider in one mount.\n */\nexport const LOCALE_LAYOUT_TEMPLATE_TS = `import { type NextLayoutIntlayer } from \"next-intlayer\";\nimport { IntlayerProvider } from \"next-intlayer/server\";\nimport { getHTMLTextDir } from \"intlayer\";\n\nexport { generateStaticParams } from \"next-intlayer\";\n\nconst LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {\n const { locale } = await params;\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerProvider locale={locale}>{children}</IntlayerProvider>\n </body>\n </html>\n );\n};\n\nexport default LocaleLayout;\n`;\n\nexport const LOCALE_LAYOUT_TEMPLATE_JS = `import { IntlayerProvider } from \"next-intlayer/server\";\nimport { getHTMLTextDir } from \"intlayer\";\n\nexport { generateStaticParams } from \"next-intlayer\";\n\nconst LocaleLayout = async ({ children, params }) => {\n const { locale } = await params;\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerProvider locale={locale}>{children}</IntlayerProvider>\n </body>\n </html>\n );\n};\n\nexport default LocaleLayout;\n`;\n\n/**\n * Locale home page (`app/[locale]/page.tsx`). Self-contained starter that does\n * not depend on an example content declaration — add Intlayer content (a\n * `*.content.ts` file consumed via `useIntlayer`) as the next step.\n */\nexport const LOCALE_PAGE_TEMPLATE_TS = `import { type NextPageIntlayer } from \"next-intlayer\";\n\nconst Page: NextPageIntlayer = async ({ params }) => {\n const { locale } = await params;\n\n return (\n <main>\n <h1>{locale}</h1>\n <p>Get started by editing this page.</p>\n </main>\n );\n};\n\nexport default Page;\n`;\n\nexport const LOCALE_PAGE_TEMPLATE_JS = `const Page = async ({ params }) => {\n const { locale } = await params;\n\n return (\n <main>\n <h1>{locale}</h1>\n <p>Get started by editing this page.</p>\n </main>\n );\n};\n\nexport default Page;\n`;\n\n/**\n * Root layout (`app/layout.tsx`) for routing modes without a locale path\n * segment (`no-prefix`, `search-params`, or no proxy at all). Owns `<html>` and\n * resolves the locale from the request (cookie / header) via `getLocale()`.\n */\nexport const UNPREFIXED_ROOT_LAYOUT_TEMPLATE_TS = `import type { PropsWithChildren } from \"react\";\nimport { getHTMLTextDir } from \"intlayer\";\nimport { getLocale, IntlayerProvider } from \"next-intlayer/server\";\n\nconst RootLayout = async ({ children }: PropsWithChildren) => {\n const locale = await getLocale();\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerProvider locale={locale}>{children}</IntlayerProvider>\n </body>\n </html>\n );\n};\n\nexport default RootLayout;\n`;\n\nexport const UNPREFIXED_ROOT_LAYOUT_TEMPLATE_JS = `import { getHTMLTextDir } from \"intlayer\";\nimport { getLocale, IntlayerProvider } from \"next-intlayer/server\";\n\nconst RootLayout = async ({ children }) => {\n const locale = await getLocale();\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerProvider locale={locale}>{children}</IntlayerProvider>\n </body>\n </html>\n );\n};\n\nexport default RootLayout;\n`;\n\n/**\n * Home page (`app/page.tsx`) for routing modes without a locale path segment.\n * Identical for TypeScript and JavaScript projects.\n */\nexport const UNPREFIXED_PAGE_TEMPLATE = `const Page = () => (\n <main>\n <h1>Hello</h1>\n <p>Get started by editing this page.</p>\n </main>\n);\n\nexport default Page;\n`;\n"],"mappings":";;;;;;;AAOA,MAAa,iBAAiB;;;;;;;;AAS9B,MAAa,sBAAsB;;;;;;;;AASnC,MAAa,0BAA0B;;;;;;AAOvC,MAAa,0BAA0B;;;;;;;;;;AAWvC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;AAqBzC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;AAyBzC,MAAa,0BAA0B;;;;;;;;;;;;;;;AAgBvC,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;AAmBvC,MAAa,qCAAqC;;;;;;;;;;;;;;;;;;AAmBlD,MAAa,qCAAqC;;;;;;;;;;;;;;;;;;;;;AAsBlD,MAAa,2BAA2B"}
|
|
@@ -88,7 +88,7 @@ const setHtmlLang = (ast) => {
|
|
|
88
88
|
* pages below the locale layout read locale/variant from it without wrapping
|
|
89
89
|
* themselves individually (see the removed `wrapPageWithProvider`).
|
|
90
90
|
*/
|
|
91
|
-
const wrapLayoutWithProvider = (code) => {
|
|
91
|
+
const wrapLayoutWithProvider = (code, { exportStaticParams = true } = {}) => {
|
|
92
92
|
const ast = parseTsx(code);
|
|
93
93
|
if (isClientComponent(ast)) return {
|
|
94
94
|
code,
|
|
@@ -120,7 +120,7 @@ const wrapLayoutWithProvider = (code) => {
|
|
|
120
120
|
};
|
|
121
121
|
ensureNamedImport(ast, "IntlayerProvider", "next-intlayer/server");
|
|
122
122
|
ensureNamedImport(ast, "getLocale", "next-intlayer/server");
|
|
123
|
-
ensureExportFrom(ast, "generateStaticParams", "next-intlayer");
|
|
123
|
+
if (exportStaticParams) ensureExportFrom(ast, "generateStaticParams", "next-intlayer");
|
|
124
124
|
ensureAwaitedLocale(funcNode);
|
|
125
125
|
setHtmlLang(ast);
|
|
126
126
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transforms.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\nimport { babelTsParser } from '../../../utils/babelParser';\nimport { ensureNamedImport, firstInsertIndex } from '../../utils/astImports';\n\nconst { builders: b } = recast.types;\n\n/** babel-ts parser handles TypeScript *and* JSX (the `typescript` parser does not). */\nconst parseTsx = (code: string): any =>\n recast.parse(code, { parser: babelTsParser });\n\n/** Result of a source transform. `code` is unchanged for any non-`wrapped` status. */\nexport type TransformResult = {\n code: string;\n status: 'wrapped' | 'already' | 'skipped-client' | 'skipped';\n};\n\n/** Detects a top-level `'use client'` directive (client components can't be async server providers). */\nconst isClientComponent = (ast: any): boolean => {\n const directives = ast.program.directives ?? [];\n if (\n directives.some((directive: any) => directive.value?.value === 'use client')\n ) {\n return true;\n }\n return ast.program.body.some(\n (stmt: any) =>\n stmt.type === 'ExpressionStatement' &&\n stmt.expression?.type === 'StringLiteral' &&\n stmt.expression.value === 'use client'\n );\n};\n\n/** Finds the function node behind `export default`, following an identifier reference if needed. */\nconst findDefaultExportFunction = (ast: any): any => {\n const body = ast.program.body;\n\n const asFunction = (node: any): any =>\n node &&\n (node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionExpression' ||\n node.type === 'FunctionDeclaration')\n ? node\n : null;\n\n for (const stmt of body) {\n if (stmt.type !== 'ExportDefaultDeclaration') continue;\n\n const direct = asFunction(stmt.declaration);\n if (direct) return direct;\n\n if (stmt.declaration?.type === 'Identifier') {\n const name = stmt.declaration.name;\n for (const candidate of body) {\n if (candidate.type === 'VariableDeclaration') {\n for (const declarator of candidate.declarations) {\n if (\n declarator.id?.type === 'Identifier' &&\n declarator.id.name === name\n ) {\n const fn = asFunction(declarator.init);\n if (fn) return fn;\n }\n }\n }\n if (\n candidate.type === 'FunctionDeclaration' &&\n candidate.id?.name === name\n ) {\n return candidate;\n }\n }\n }\n }\n\n return null;\n};\n\n/** Ensures `export { exportedName } from source`, skipping when already declared/re-exported. */\nconst ensureExportFrom = (\n ast: any,\n exportedName: string,\n source: string\n): void => {\n const body = ast.program.body;\n\n const alreadyPresent = body.some((stmt: any) => {\n if (\n stmt.type === 'ExportNamedDeclaration' &&\n stmt.source?.value === source\n ) {\n return stmt.specifiers.some(\n (spec: any) => spec.exported?.name === exportedName\n );\n }\n // Locally declared `export const/function generateStaticParams`\n if (stmt.type === 'ExportNamedDeclaration' && stmt.declaration) {\n const decl = stmt.declaration;\n if (decl.type === 'FunctionDeclaration' && decl.id?.name === exportedName)\n return true;\n if (decl.type === 'VariableDeclaration') {\n return decl.declarations.some(\n (d: any) => d.id?.type === 'Identifier' && d.id.name === exportedName\n );\n }\n }\n return false;\n });\n\n if (alreadyPresent) return;\n\n const exportNode = parseTsx(`export { ${exportedName} } from \"${source}\";`)\n .program.body[0];\n ast.program.body.splice(firstInsertIndex(ast), 0, exportNode);\n};\n\n/** Makes the function async and inserts `const locale = await getLocale();` once, at the top of its body. */\nconst ensureAwaitedLocale = (funcNode: any): void => {\n funcNode.async = true;\n\n if (funcNode.body.type !== 'BlockStatement') {\n funcNode.body = b.blockStatement([b.returnStatement(funcNode.body)]);\n }\n\n const hasLocale = funcNode.body.body.some(\n (stmt: any) =>\n stmt.type === 'VariableDeclaration' &&\n stmt.declarations.some(\n (d: any) =>\n // const locale = ...\n (d.id?.type === 'Identifier' && d.id.name === 'locale') ||\n // const { locale } = ... or const { locale: locale } = ...\n (d.id?.type === 'ObjectPattern' &&\n d.id.properties?.some(\n (prop: any) =>\n prop.value?.type === 'Identifier' &&\n prop.value?.name === 'locale'\n ))\n )\n );\n\n if (!hasLocale) {\n const localeStatement = parseTsx('const locale = await getLocale();')\n .program.body[0];\n funcNode.body.body.unshift(localeStatement);\n }\n};\n\n/** Builds `<providerName locale={locale}>{child}</providerName>` around an existing JSX child node. */\nconst buildProviderElement = (providerName: string, childNode: any): any => {\n const template = parseTsx(\n `const __wrap = <${providerName} locale={locale}>{__child__}</${providerName}>;`\n );\n const providerElement = template.program.body[0].declarations[0].init;\n providerElement.children = [childNode];\n return providerElement;\n};\n\n/** Sets `lang={locale}` on the first `<html>` element, if present. */\nconst setHtmlLang = (ast: any): void => {\n recast.visit(ast, {\n visitJSXOpeningElement(path) {\n const node = path.node;\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'html') {\n const langAttr = node.attributes?.find(\n (attr: any) =>\n attr.type === 'JSXAttribute' && attr.name?.name === 'lang'\n ) as any;\n const localeExpression = b.jsxExpressionContainer(\n b.identifier('locale')\n );\n if (langAttr) {\n langAttr.value = localeExpression;\n } else {\n node.attributes.push(\n b.jsxAttribute(b.jsxIdentifier('lang'), localeExpression)\n );\n }\n return false;\n }\n this.traverse(path);\n },\n });\n};\n\n/**\n * Wraps the `{children}` of a Next.js App Router **layout** with the unified\n * `IntlayerProvider`, deriving the locale via `getLocale()`. Safe and\n * idempotent: bails (returns the original code) for client components, when no\n * `{children}` placeholder is found, or when there is no default export.\n *\n * `IntlayerProvider` seeds both the server context and the client provider in\n * one mount, so this is the only provider a Next.js App Router app needs —\n * pages below the locale layout read locale/variant from it without wrapping\n * themselves individually (see the removed `wrapPageWithProvider`).\n */\nexport const wrapLayoutWithProvider = (code: string): TransformResult => {\n const ast = parseTsx(code);\n\n if (isClientComponent(ast)) return { code, status: 'skipped-client' };\n if (code.includes('IntlayerProvider')) return { code, status: 'already' };\n\n const funcNode = findDefaultExportFunction(ast);\n if (!funcNode) return { code, status: 'skipped' };\n\n let wrapped = false;\n recast.visit(funcNode, {\n visitJSXExpressionContainer(path) {\n if (wrapped) return false;\n const expression = path.node.expression;\n if (expression?.type === 'Identifier' && expression.name === 'children') {\n path.replace(buildProviderElement('IntlayerProvider', path.node));\n wrapped = true;\n return false;\n }\n this.traverse(path);\n },\n });\n\n if (!wrapped) return { code, status: 'skipped' };\n\n ensureNamedImport(ast, 'IntlayerProvider', 'next-intlayer/server');\n ensureNamedImport(ast, 'getLocale', 'next-intlayer/server');\n ensureExportFrom(ast, 'generateStaticParams', 'next-intlayer');\n ensureAwaitedLocale(funcNode);\n setHtmlLang(ast);\n\n return { code: recast.print(ast).code, status: 'wrapped' };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transforms.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\nimport { babelTsParser } from '../../../utils/babelParser';\nimport { ensureNamedImport, firstInsertIndex } from '../../utils/astImports';\n\nconst { builders: b } = recast.types;\n\n/** babel-ts parser handles TypeScript *and* JSX (the `typescript` parser does not). */\nconst parseTsx = (code: string): any =>\n recast.parse(code, { parser: babelTsParser });\n\n/** Result of a source transform. `code` is unchanged for any non-`wrapped` status. */\nexport type TransformResult = {\n code: string;\n status: 'wrapped' | 'already' | 'skipped-client' | 'skipped';\n};\n\n/** Detects a top-level `'use client'` directive (client components can't be async server providers). */\nconst isClientComponent = (ast: any): boolean => {\n const directives = ast.program.directives ?? [];\n if (\n directives.some((directive: any) => directive.value?.value === 'use client')\n ) {\n return true;\n }\n return ast.program.body.some(\n (stmt: any) =>\n stmt.type === 'ExpressionStatement' &&\n stmt.expression?.type === 'StringLiteral' &&\n stmt.expression.value === 'use client'\n );\n};\n\n/** Finds the function node behind `export default`, following an identifier reference if needed. */\nconst findDefaultExportFunction = (ast: any): any => {\n const body = ast.program.body;\n\n const asFunction = (node: any): any =>\n node &&\n (node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionExpression' ||\n node.type === 'FunctionDeclaration')\n ? node\n : null;\n\n for (const stmt of body) {\n if (stmt.type !== 'ExportDefaultDeclaration') continue;\n\n const direct = asFunction(stmt.declaration);\n if (direct) return direct;\n\n if (stmt.declaration?.type === 'Identifier') {\n const name = stmt.declaration.name;\n for (const candidate of body) {\n if (candidate.type === 'VariableDeclaration') {\n for (const declarator of candidate.declarations) {\n if (\n declarator.id?.type === 'Identifier' &&\n declarator.id.name === name\n ) {\n const fn = asFunction(declarator.init);\n if (fn) return fn;\n }\n }\n }\n if (\n candidate.type === 'FunctionDeclaration' &&\n candidate.id?.name === name\n ) {\n return candidate;\n }\n }\n }\n }\n\n return null;\n};\n\n/** Ensures `export { exportedName } from source`, skipping when already declared/re-exported. */\nconst ensureExportFrom = (\n ast: any,\n exportedName: string,\n source: string\n): void => {\n const body = ast.program.body;\n\n const alreadyPresent = body.some((stmt: any) => {\n if (\n stmt.type === 'ExportNamedDeclaration' &&\n stmt.source?.value === source\n ) {\n return stmt.specifiers.some(\n (spec: any) => spec.exported?.name === exportedName\n );\n }\n // Locally declared `export const/function generateStaticParams`\n if (stmt.type === 'ExportNamedDeclaration' && stmt.declaration) {\n const decl = stmt.declaration;\n if (decl.type === 'FunctionDeclaration' && decl.id?.name === exportedName)\n return true;\n if (decl.type === 'VariableDeclaration') {\n return decl.declarations.some(\n (d: any) => d.id?.type === 'Identifier' && d.id.name === exportedName\n );\n }\n }\n return false;\n });\n\n if (alreadyPresent) return;\n\n const exportNode = parseTsx(`export { ${exportedName} } from \"${source}\";`)\n .program.body[0];\n ast.program.body.splice(firstInsertIndex(ast), 0, exportNode);\n};\n\n/** Makes the function async and inserts `const locale = await getLocale();` once, at the top of its body. */\nconst ensureAwaitedLocale = (funcNode: any): void => {\n funcNode.async = true;\n\n if (funcNode.body.type !== 'BlockStatement') {\n funcNode.body = b.blockStatement([b.returnStatement(funcNode.body)]);\n }\n\n const hasLocale = funcNode.body.body.some(\n (stmt: any) =>\n stmt.type === 'VariableDeclaration' &&\n stmt.declarations.some(\n (d: any) =>\n // const locale = ...\n (d.id?.type === 'Identifier' && d.id.name === 'locale') ||\n // const { locale } = ... or const { locale: locale } = ...\n (d.id?.type === 'ObjectPattern' &&\n d.id.properties?.some(\n (prop: any) =>\n prop.value?.type === 'Identifier' &&\n prop.value?.name === 'locale'\n ))\n )\n );\n\n if (!hasLocale) {\n const localeStatement = parseTsx('const locale = await getLocale();')\n .program.body[0];\n funcNode.body.body.unshift(localeStatement);\n }\n};\n\n/** Builds `<providerName locale={locale}>{child}</providerName>` around an existing JSX child node. */\nconst buildProviderElement = (providerName: string, childNode: any): any => {\n const template = parseTsx(\n `const __wrap = <${providerName} locale={locale}>{__child__}</${providerName}>;`\n );\n const providerElement = template.program.body[0].declarations[0].init;\n providerElement.children = [childNode];\n return providerElement;\n};\n\n/** Sets `lang={locale}` on the first `<html>` element, if present. */\nconst setHtmlLang = (ast: any): void => {\n recast.visit(ast, {\n visitJSXOpeningElement(path) {\n const node = path.node;\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'html') {\n const langAttr = node.attributes?.find(\n (attr: any) =>\n attr.type === 'JSXAttribute' && attr.name?.name === 'lang'\n ) as any;\n const localeExpression = b.jsxExpressionContainer(\n b.identifier('locale')\n );\n if (langAttr) {\n langAttr.value = localeExpression;\n } else {\n node.attributes.push(\n b.jsxAttribute(b.jsxIdentifier('lang'), localeExpression)\n );\n }\n return false;\n }\n this.traverse(path);\n },\n });\n};\n\n/** Options of {@link wrapLayoutWithProvider}. */\nexport type WrapLayoutOptions = {\n /**\n * Re-export `generateStaticParams` from `next-intlayer`. Only meaningful for a\n * layout under a `[locale]` segment; a root layout of an unprefixed app has\n * no locale param to pre-render. Default: `true`.\n */\n exportStaticParams?: boolean;\n};\n\n/**\n * Wraps the `{children}` of a Next.js App Router **layout** with the unified\n * `IntlayerProvider`, deriving the locale via `getLocale()`. Safe and\n * idempotent: bails (returns the original code) for client components, when no\n * `{children}` placeholder is found, or when there is no default export.\n *\n * `IntlayerProvider` seeds both the server context and the client provider in\n * one mount, so this is the only provider a Next.js App Router app needs —\n * pages below the locale layout read locale/variant from it without wrapping\n * themselves individually (see the removed `wrapPageWithProvider`).\n */\nexport const wrapLayoutWithProvider = (\n code: string,\n { exportStaticParams = true }: WrapLayoutOptions = {}\n): TransformResult => {\n const ast = parseTsx(code);\n\n if (isClientComponent(ast)) return { code, status: 'skipped-client' };\n if (code.includes('IntlayerProvider')) return { code, status: 'already' };\n\n const funcNode = findDefaultExportFunction(ast);\n if (!funcNode) return { code, status: 'skipped' };\n\n let wrapped = false;\n recast.visit(funcNode, {\n visitJSXExpressionContainer(path) {\n if (wrapped) return false;\n const expression = path.node.expression;\n if (expression?.type === 'Identifier' && expression.name === 'children') {\n path.replace(buildProviderElement('IntlayerProvider', path.node));\n wrapped = true;\n return false;\n }\n this.traverse(path);\n },\n });\n\n if (!wrapped) return { code, status: 'skipped' };\n\n ensureNamedImport(ast, 'IntlayerProvider', 'next-intlayer/server');\n ensureNamedImport(ast, 'getLocale', 'next-intlayer/server');\n if (exportStaticParams) {\n ensureExportFrom(ast, 'generateStaticParams', 'next-intlayer');\n }\n ensureAwaitedLocale(funcNode);\n setHtmlLang(ast);\n\n return { code: recast.print(ast).code, status: 'wrapped' };\n};\n"],"mappings":"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"}
|