Skip to content

Commit 022065c

Browse files
authored
️💥 feat: Update scaffolding to handle react router and new structure (#155)
1 parent 4032e2d commit 022065c

File tree

16 files changed

+552
-84
lines changed

16 files changed

+552
-84
lines changed

packages/create-drupal-decoupled/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@octahedroid/create-drupal-decoupled",
3-
"version": "0.4.1",
3+
"version": "0.5.0",
44
"packageManager": "[email protected]",
55
"description": "Scaffold the integration with Drupal in a decoupled frontend",
66
"keywords": [
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
export const SUPPORTED_FRONTENDS = ['remix', 'next'] as const
1+
export const SUPPORTED_FRONTENDS = ['remix', 'next', 'react-router'] as const
22

33
export type SupportedFrontend = (typeof SUPPORTED_FRONTENDS)[number]
44

55
export const FRONTEND_MACHINE_NAME_TO_READABLE_NAME: Record<
66
SupportedFrontend,
7-
Capitalize<SupportedFrontend>
7+
string
88
> = {
99
remix: 'Remix',
1010
next: 'Next',
11+
'react-router': 'React-Router',
1112
} as const
1213

13-
export function getFrontendReadableName(
14-
frontendName: SupportedFrontend
15-
) {
14+
export function getFrontendReadableName(frontendName: SupportedFrontend) {
1615
return FRONTEND_MACHINE_NAME_TO_READABLE_NAME[frontendName]
1716
}
1817

19-
export function isSupportedFrontend(frontend: string): frontend is SupportedFrontend {
18+
export function isSupportedFrontend(
19+
frontend: string
20+
): frontend is SupportedFrontend {
2021
return SUPPORTED_FRONTENDS.includes(frontend as SupportedFrontend)
21-
}
22+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const DrupalClientFile = {
2+
targetPath: 'app/utils',
3+
sourcePath: 'shared',
4+
fileName: 'client.server.ts',
5+
operation: 'create',
6+
} satisfies FilesConfig
7+
8+
export const DrupalAuthFile = {
9+
targetPath: 'app/utils',
10+
sourcePath: 'shared',
11+
fileName: 'auth.server.ts',
12+
operation: 'create',
13+
} satisfies FilesConfig
14+
15+
export const TailwindCSSFile = {
16+
targetPath: 'app',
17+
sourcePath: 'shared',
18+
fileName: 'tailwind.css',
19+
operation: 'update',
20+
} satisfies FilesConfig
21+
22+
export const EnvFile = {
23+
targetPath: '.',
24+
sourcePath: 'shared',
25+
fileName: '.env.example',
26+
operation: 'create',
27+
} satisfies FilesConfig
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
type SourcePath = `${SupportedFrontend}-graphql` | 'shared'
2+
3+
type DeleteFileConfig = {
4+
targetPath: string
5+
fileName: string
6+
operation: 'delete'
7+
}
8+
9+
type CreateFileConfig = {
10+
targetPath: string
11+
sourcePath: SourcePath
12+
fileName: string
13+
rename?: string
14+
sanitize?: (content: string) => string
15+
operation: 'create' | 'update'
16+
}
17+
18+
type FilesConfig = DeleteFileConfig | CreateFileConfig
19+
20+
type ScaffoldFilesPerFrontend = Record<
21+
SupportedFrontend,
22+
{
23+
files: FilesConfig[]
24+
}
25+
>
Lines changed: 161 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,141 @@
11
import fs from 'node:fs'
22
import path from 'node:path'
33
import type { SupportedFrontend } from 'src/constants'
4-
5-
type SourcePath = `${SupportedFrontend}-graphql` | 'shared'
6-
7-
type FilesConfig = {
8-
targetPath: string
9-
sourcePath: SourcePath
10-
fileName: string
11-
rename?: string
12-
}
13-
14-
type ScaffoldFilesPerFrontend = Record<
15-
SupportedFrontend,
16-
{
17-
files: FilesConfig[]
18-
}
19-
>
4+
import {
5+
DrupalAuthFile,
6+
DrupalClientFile,
7+
EnvFile,
8+
TailwindCSSFile,
9+
} from './files/shared'
2010

2111
const SCAFFOLD_FILES_PER_FRONTEND: Readonly<ScaffoldFilesPerFrontend> = {
2212
remix: {
2313
files: [
14+
DrupalClientFile,
15+
DrupalAuthFile,
16+
EnvFile,
2417
{
25-
targetPath: 'app/utils/drupal',
26-
sourcePath: 'shared',
27-
fileName: 'client.server.ts',
18+
...EnvFile,
19+
rename: '.env',
20+
operation: 'update',
2821
},
2922
{
30-
targetPath: 'app/utils/drupal',
23+
targetPath: 'app',
24+
sourcePath: 'remix-graphql',
25+
fileName: 'tailwind.css',
26+
operation: 'update',
27+
},
28+
{
29+
targetPath: 'app/utils',
3130
sourcePath: 'remix-graphql',
3231
fileName: 'calculate-path.server.ts',
32+
operation: 'create',
3333
},
3434
{
3535
targetPath: 'app/routes',
3636
sourcePath: 'remix-graphql',
3737
fileName: '$.tsx',
38+
operation: 'create',
3839
},
3940
{
40-
targetPath: 'app',
41-
sourcePath: 'shared',
42-
fileName: 'tailwind.css',
43-
},
44-
{
45-
targetPath: '.',
46-
sourcePath: 'shared',
47-
fileName: '.env.example',
41+
targetPath: 'app/routes',
42+
sourcePath: 'remix-graphql',
43+
fileName: '$.tsx',
44+
rename: '_index.tsx',
45+
operation: 'update',
4846
},
4947
],
5048
},
5149
next: {
5250
files: [
5351
{
54-
targetPath: 'utils/drupal',
55-
sourcePath: 'shared',
56-
fileName: 'client.server.ts',
52+
...DrupalClientFile,
53+
targetPath: '/utils',
5754
rename: 'client.ts',
55+
sanitize: (fileContent: string) =>
56+
fileContent.replace(
57+
"import { getToken } from './auth.server'",
58+
"import { getToken } from './auth'"
59+
),
60+
},
61+
{
62+
...DrupalAuthFile,
63+
targetPath: '/utils',
64+
rename: 'auth.ts',
65+
},
66+
{
67+
...TailwindCSSFile,
68+
rename: 'globals.css',
5869
},
70+
EnvFile,
5971
{
60-
targetPath: 'utils/drupal',
72+
...EnvFile,
73+
rename: '.env.local',
74+
operation: 'update',
75+
},
76+
{
77+
targetPath: 'utils',
6178
sourcePath: 'next-graphql',
6279
fileName: 'calculate-path.ts',
80+
operation: 'create',
6381
},
6482
{
65-
targetPath: 'app/[...slug]',
83+
targetPath: 'app/[[...slug]]',
6684
sourcePath: 'next-graphql',
6785
fileName: 'page.tsx',
86+
operation: 'create',
6887
},
6988
{
89+
operation: 'delete',
7090
targetPath: 'app',
71-
sourcePath: 'shared',
72-
fileName: 'tailwind.css',
73-
rename: 'globals.css',
91+
fileName: 'page.tsx',
92+
},
93+
],
94+
},
95+
'react-router': {
96+
files: [
97+
DrupalClientFile,
98+
DrupalAuthFile,
99+
EnvFile,
100+
{
101+
...EnvFile,
102+
rename: '.env',
103+
operation: 'update',
74104
},
75105
{
76-
targetPath: '.',
106+
...TailwindCSSFile,
107+
rename: 'app.css',
108+
operation: 'update',
109+
},
110+
{
111+
targetPath: 'app/utils',
77112
sourcePath: 'shared',
78-
fileName: '.env.example',
113+
fileName: 'metatags.ts',
114+
operation: 'create',
115+
},
116+
{
117+
targetPath: 'app',
118+
sourcePath: 'react-router-graphql',
119+
fileName: 'routes.ts',
120+
operation: 'update',
121+
},
122+
{
123+
targetPath: 'app/routes',
124+
sourcePath: 'react-router-graphql',
125+
fileName: '$.tsx',
126+
operation: 'create',
127+
},
128+
{
129+
targetPath: 'app/routes',
130+
fileName: 'home.tsx',
131+
operation: 'delete',
132+
},
133+
{
134+
targetPath: 'app/utils',
135+
sourcePath: 'react-router-graphql',
136+
fileName: 'calculatePath.ts',
137+
rename: 'routes.ts',
138+
operation: 'create',
79139
},
80140
],
81141
},
@@ -85,13 +145,65 @@ export function isJavascriptProject(projectPath: string) {
85145
return fs.existsSync(path.join(projectPath, 'package.json'))
86146
}
87147

148+
// Validate if the project
149+
export function isValidFramework(
150+
projectPath: string,
151+
frontend: SupportedFrontend
152+
) {
153+
const packageJsonPath = path.join(projectPath, 'package.json')
154+
if (!fs.existsSync(packageJsonPath)) {
155+
return false
156+
}
157+
158+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
159+
const dependencies = packageJson.dependencies || {}
160+
const devDependencies = packageJson.devDependencies || {}
161+
162+
if (frontend === 'next') {
163+
return Boolean(dependencies['next'])
164+
}
165+
166+
if (frontend === 'remix') {
167+
return Boolean(devDependencies['@remix-run/dev'])
168+
}
169+
170+
if (frontend === 'react-router') {
171+
return Boolean(devDependencies['@react-router/dev'])
172+
}
173+
174+
return false
175+
}
176+
177+
export function updateGitignore(
178+
frontend: SupportedFrontend,
179+
projectPath: string
180+
) {
181+
const gitignorePath = path.join(projectPath, '.gitignore')
182+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8')
183+
const envFileName = frontend === 'next' ? '.env*' : '.env'
184+
185+
if (!gitignoreContent.includes(envFileName)) {
186+
fs.appendFileSync(gitignorePath, `\n${envFileName}\n`)
187+
}
188+
}
189+
88190
export function scaffoldFrontend(
89191
frontend: SupportedFrontend,
90192
projectPath: string
91193
) {
92194
const frontendFiles = SCAFFOLD_FILES_PER_FRONTEND[frontend].files
93195

94-
return frontendFiles.map(({ targetPath, sourcePath, fileName, rename }) => {
196+
return frontendFiles.map((config) => {
197+
const { operation, fileName, targetPath } = config
198+
199+
if (operation === 'delete') {
200+
const filePath = path.join(projectPath, targetPath, fileName)
201+
if (fs.existsSync(filePath)) {
202+
fs.rmSync(filePath, { recursive: true, force: true })
203+
}
204+
return `Deleted ${targetPath === '.' ? '' : `${targetPath}/`}${fileName}`
205+
}
206+
const { sourcePath, rename, sanitize } = config
95207
const name = rename || fileName
96208

97209
const templatePath = path.join(__dirname, 'templates', sourcePath, fileName)
@@ -101,10 +213,17 @@ export function scaffoldFrontend(
101213
targetPath,
102214
name
103215
)
104-
105216
fs.mkdirSync(path.dirname(destinationPath), { recursive: true })
106-
fs.copyFileSync(templatePath, destinationPath)
107217

108-
return `${targetPath === '.' ? '' : `${targetPath}/`}${name}`
218+
let fileContent = fs.readFileSync(templatePath, 'utf-8')
219+
220+
if (sanitize) {
221+
fileContent = sanitize(fileContent)
222+
}
223+
224+
fs.writeFileSync(destinationPath, fileContent)
225+
226+
const message = operation === 'update' ? 'Updated' : 'Created'
227+
return `${message} ${targetPath === '.' ? '' : `${targetPath}/`}${name}`
109228
})
110229
}

0 commit comments

Comments
 (0)