@lukoweb/create-apitogo 0.2.22 → 0.2.24
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/LICENSE.md +18 -18
- package/README.md +60 -60
- package/dist/index.js +0 -1
- package/dist/templates/default/js/AGENTS.md +35 -35
- package/dist/templates/default/js/README-template.md +24 -24
- package/dist/templates/default/js/apis/openapi.yaml +806 -806
- package/dist/templates/default/js/apitogo.config.json +256 -256
- package/dist/templates/default/js/apitogo.config.jsx +3 -3
- package/dist/templates/default/js/env +23 -23
- package/dist/templates/default/js/eslintrc.json +4 -4
- package/dist/templates/default/js/gitignore +32 -32
- package/dist/templates/default/js/pages/example.mdx +131 -131
- package/dist/templates/default/js/pages/introduction.mdx +20 -20
- package/dist/templates/default/js/public/logo-dark.svg +4 -4
- package/dist/templates/default/js/public/logo-light.svg +4 -4
- package/dist/templates/default/js/user-panel/AccountPlansPage.tsx +1 -1
- package/dist/templates/default/js/user-panel/DashboardPage.tsx +1 -1
- package/dist/templates/default/ts/AGENTS.md +35 -35
- package/dist/templates/default/ts/README-template.md +24 -24
- package/dist/templates/default/ts/apis/openapi.yaml +806 -806
- package/dist/templates/default/ts/apitogo.config.json +221 -221
- package/dist/templates/default/ts/apitogo.config.tsx +8 -8
- package/dist/templates/default/ts/env +23 -23
- package/dist/templates/default/ts/eslintrc.json +6 -6
- package/dist/templates/default/ts/gitignore +32 -32
- package/dist/templates/default/ts/landing/LandingPage.tsx +176 -176
- package/dist/templates/default/ts/pages/example.mdx +132 -132
- package/dist/templates/default/ts/pages/introduction.mdx +21 -21
- package/dist/templates/default/ts/public/logo-dark.svg +4 -4
- package/dist/templates/default/ts/public/logo-light.svg +4 -4
- package/dist/templates/default/ts/tsconfig.json +16 -16
- package/dist/templates/default/ts/user-panel/AccountPlansPage.tsx +1 -1
- package/dist/templates/default/ts/user-panel/DashboardPage.tsx +1 -1
- package/dist/templates/empty/js/AGENTS.md +35 -35
- package/dist/templates/empty/js/README-template.md +23 -23
- package/dist/templates/empty/js/apitogo.config.json +80 -80
- package/dist/templates/empty/js/apitogo.config.jsx +6 -6
- package/dist/templates/empty/js/env +23 -23
- package/dist/templates/empty/js/eslintrc.json +4 -4
- package/dist/templates/empty/js/gitignore +32 -32
- package/dist/templates/empty/js/public/logo-dark.svg +4 -4
- package/dist/templates/empty/js/public/logo-light.svg +4 -4
- package/dist/templates/empty/js/user-panel/AccountPlansPage.tsx +1 -1
- package/dist/templates/empty/js/user-panel/DashboardPage.tsx +1 -1
- package/dist/templates/empty/ts/AGENTS.md +35 -35
- package/dist/templates/empty/ts/README-template.md +23 -23
- package/dist/templates/empty/ts/apitogo.config.json +81 -81
- package/dist/templates/empty/ts/apitogo.config.tsx +8 -8
- package/dist/templates/empty/ts/env +23 -23
- package/dist/templates/empty/ts/eslintrc.json +6 -6
- package/dist/templates/empty/ts/gitignore +32 -32
- package/dist/templates/empty/ts/landing/LandingPage.tsx +43 -43
- package/dist/templates/empty/ts/public/logo-dark.svg +4 -4
- package/dist/templates/empty/ts/public/logo-light.svg +4 -4
- package/dist/templates/empty/ts/tsconfig.json +16 -16
- package/dist/templates/empty/ts/user-panel/AccountPlansPage.tsx +1 -1
- package/dist/templates/empty/ts/user-panel/DashboardPage.tsx +1 -1
- package/dist/templates/index.ts +422 -422
- package/dist/templates/types.ts +23 -23
- package/package.json +1 -1
package/dist/templates/index.ts
CHANGED
|
@@ -1,422 +1,422 @@
|
|
|
1
|
-
// biome-ignore-all lint/suspicious/noConsole: Logging allowed here
|
|
2
|
-
import { execFileSync } from "node:child_process";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { bold, cyan } from "picocolors";
|
|
7
|
-
import { copy } from "../helpers/copy";
|
|
8
|
-
import { install } from "../helpers/install";
|
|
9
|
-
import type { GetTemplateFileArgs, InstallTemplateArgs } from "./types";
|
|
10
|
-
|
|
11
|
-
// When bundled by ncc, __dirname points to dist/, but templates are at dist/templates/
|
|
12
|
-
const PACKAGE_ROOT = __dirname.endsWith("dist")
|
|
13
|
-
? path.dirname(__dirname)
|
|
14
|
-
: path.dirname(__dirname);
|
|
15
|
-
const TEMPLATES_DIR = __dirname.endsWith("dist")
|
|
16
|
-
? path.join(__dirname, "templates")
|
|
17
|
-
: __dirname;
|
|
18
|
-
const WORKSPACE_ROOT = path.resolve(PACKAGE_ROOT, "../..");
|
|
19
|
-
const WINDOWS_SHELL = process.env.ComSpec ?? "cmd.exe";
|
|
20
|
-
|
|
21
|
-
const formatAppTitle = (appName: string) =>
|
|
22
|
-
appName
|
|
23
|
-
.split(/[-_\s]+/)
|
|
24
|
-
.filter(Boolean)
|
|
25
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
26
|
-
.join(" ");
|
|
27
|
-
|
|
28
|
-
const resolveCoreApitogoPackageName = async (): Promise<string> => {
|
|
29
|
-
const createPackageJson = JSON.parse(
|
|
30
|
-
await fs.readFile(path.join(PACKAGE_ROOT, "package.json"), "utf8"),
|
|
31
|
-
) as { name: string };
|
|
32
|
-
return createPackageJson.name.replace("/create-apitogo", "/apitogo");
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** Primary npm scope; split so secondary publish does not rewrite this guard. */
|
|
36
|
-
const PRIMARY_APITOGO_SCOPE = "@lukow" + "eb";
|
|
37
|
-
|
|
38
|
-
const normalizeDependencyVersion = (version: string): string =>
|
|
39
|
-
version.replace(/^[\^~>=<]+/, "");
|
|
40
|
-
|
|
41
|
-
const LUCIDE_REACT_VERSION = "0.577.0";
|
|
42
|
-
|
|
43
|
-
const ensureRequiredApitogoModules = (
|
|
44
|
-
dependencies: Record<string, string>,
|
|
45
|
-
corePackageName: string,
|
|
46
|
-
version: string,
|
|
47
|
-
) => {
|
|
48
|
-
const scope = corePackageName.slice(0, corePackageName.indexOf("/"));
|
|
49
|
-
const landing = `${scope}/apitogo-module-landing`;
|
|
50
|
-
const userPanel = `${scope}/apitogo-module-user-panel`;
|
|
51
|
-
const billing = `${scope}/apitogo-plugin-dev-portal-billing`;
|
|
52
|
-
const moduleVersion = `^${normalizeDependencyVersion(version)}`;
|
|
53
|
-
|
|
54
|
-
dependencies[landing] ??= moduleVersion;
|
|
55
|
-
dependencies[userPanel] ??= moduleVersion;
|
|
56
|
-
dependencies[billing] ??= moduleVersion;
|
|
57
|
-
dependencies["lucide-react"] ??= LUCIDE_REACT_VERSION;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const ensureSecondaryApitogoAliases = (
|
|
61
|
-
dependencies: Record<string, string>,
|
|
62
|
-
corePackageName: string,
|
|
63
|
-
version: string,
|
|
64
|
-
) => {
|
|
65
|
-
if (corePackageName.startsWith(`${PRIMARY_APITOGO_SCOPE}/`)) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const alias = (target: string) =>
|
|
70
|
-
`npm:${target}@${normalizeDependencyVersion(version)}`;
|
|
71
|
-
const scope = corePackageName.slice(0, corePackageName.indexOf("/"));
|
|
72
|
-
|
|
73
|
-
dependencies[`${PRIMARY_APITOGO_SCOPE}/apitogo`] = alias(corePackageName);
|
|
74
|
-
dependencies[`${PRIMARY_APITOGO_SCOPE}/apitogo-module-landing`] = alias(
|
|
75
|
-
`${scope}/apitogo-module-landing`,
|
|
76
|
-
);
|
|
77
|
-
dependencies[`${PRIMARY_APITOGO_SCOPE}/apitogo-module-user-panel`] = alias(
|
|
78
|
-
`${scope}/apitogo-module-user-panel`,
|
|
79
|
-
);
|
|
80
|
-
dependencies[`${PRIMARY_APITOGO_SCOPE}/apitogo-plugin-dev-portal-billing`] =
|
|
81
|
-
alias(`${scope}/apitogo-plugin-dev-portal-billing`);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const parseWorkspaceCatalog = async (workspaceFile: string) => {
|
|
85
|
-
const content = await fs.readFile(workspaceFile, "utf8");
|
|
86
|
-
const catalog: Record<string, string> = {};
|
|
87
|
-
let inCatalog = false;
|
|
88
|
-
|
|
89
|
-
for (const line of content.split(/\r?\n/)) {
|
|
90
|
-
if (!inCatalog) {
|
|
91
|
-
inCatalog = line.trim() === "catalog:";
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!line.startsWith(" ")) break;
|
|
96
|
-
|
|
97
|
-
const match = line.match(/^ {2}(?:"([^"]+)"|([^:]+)):\s+(.+)$/);
|
|
98
|
-
if (!match) continue;
|
|
99
|
-
|
|
100
|
-
const dependency = match[1] ?? match[2]?.trim();
|
|
101
|
-
const version = match[3]?.trim();
|
|
102
|
-
if (dependency && version) catalog[dependency] = version;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return catalog;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const replaceCatalogVersions = (
|
|
109
|
-
value: unknown,
|
|
110
|
-
catalog: Record<string, string>,
|
|
111
|
-
): unknown => {
|
|
112
|
-
if (Array.isArray(value)) {
|
|
113
|
-
return value.map((entry) => replaceCatalogVersions(entry, catalog));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!value || typeof value !== "object") {
|
|
117
|
-
return value;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return Object.fromEntries(
|
|
121
|
-
Object.entries(value).map(([key, entry]) => [
|
|
122
|
-
key,
|
|
123
|
-
entry === "catalog:"
|
|
124
|
-
? (catalog[key] ?? entry)
|
|
125
|
-
: replaceCatalogVersions(entry, catalog),
|
|
126
|
-
]),
|
|
127
|
-
);
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const stageLocalZudokuPackage = async (
|
|
131
|
-
sourceDir: string,
|
|
132
|
-
stagingDir: string,
|
|
133
|
-
) => {
|
|
134
|
-
const workspaceCatalog = await parseWorkspaceCatalog(
|
|
135
|
-
path.join(WORKSPACE_ROOT, "pnpm-workspace.yaml"),
|
|
136
|
-
);
|
|
137
|
-
const packageJsonPath = path.join(sourceDir, "package.json");
|
|
138
|
-
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
139
|
-
const stagedPackageJson = replaceCatalogVersions(
|
|
140
|
-
packageJson,
|
|
141
|
-
workspaceCatalog,
|
|
142
|
-
) as typeof packageJson;
|
|
143
|
-
const packageFiles = new Set<string>([
|
|
144
|
-
"package.json",
|
|
145
|
-
...(Array.isArray(packageJson.files) ? packageJson.files : []),
|
|
146
|
-
]);
|
|
147
|
-
|
|
148
|
-
await fs.rm(stagingDir, { recursive: true, force: true });
|
|
149
|
-
await fs.mkdir(stagingDir, { recursive: true });
|
|
150
|
-
|
|
151
|
-
for (const entry of packageFiles) {
|
|
152
|
-
if (entry === "package.json") continue;
|
|
153
|
-
|
|
154
|
-
const sourcePath = path.join(sourceDir, entry);
|
|
155
|
-
const destinationPath = path.join(stagingDir, entry);
|
|
156
|
-
const stat = await fs.stat(sourcePath).catch(() => null);
|
|
157
|
-
|
|
158
|
-
if (!stat) continue;
|
|
159
|
-
|
|
160
|
-
if (stat.isDirectory()) {
|
|
161
|
-
await fs.cp(sourcePath, destinationPath, { recursive: true });
|
|
162
|
-
} else {
|
|
163
|
-
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
164
|
-
await fs.copyFile(sourcePath, destinationPath);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
delete stagedPackageJson.scripts?.prepublishOnly;
|
|
169
|
-
delete stagedPackageJson.scripts?.postpublish;
|
|
170
|
-
if (
|
|
171
|
-
stagedPackageJson.scripts &&
|
|
172
|
-
Object.keys(stagedPackageJson.scripts).length === 0
|
|
173
|
-
) {
|
|
174
|
-
delete stagedPackageJson.scripts;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
await fs.writeFile(
|
|
178
|
-
path.join(stagingDir, "package.json"),
|
|
179
|
-
JSON.stringify(stagedPackageJson, null, 2) + os.EOL,
|
|
180
|
-
);
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Get the file path for a given file in a template, e.g. "next.config.js".
|
|
185
|
-
*/
|
|
186
|
-
export const getTemplateFile = ({
|
|
187
|
-
template,
|
|
188
|
-
mode,
|
|
189
|
-
file,
|
|
190
|
-
}: GetTemplateFileArgs): string => {
|
|
191
|
-
return path.join(TEMPLATES_DIR, template, mode, file);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Install a Zudoku internal template to a given `root` directory.
|
|
196
|
-
*/
|
|
197
|
-
export const installTemplate = async ({
|
|
198
|
-
appName,
|
|
199
|
-
root,
|
|
200
|
-
packageManager,
|
|
201
|
-
isOnline,
|
|
202
|
-
template,
|
|
203
|
-
mode,
|
|
204
|
-
eslint,
|
|
205
|
-
skipInstall,
|
|
206
|
-
zudokuVersion,
|
|
207
|
-
}: InstallTemplateArgs) => {
|
|
208
|
-
console.log(bold(`Using ${packageManager}.`));
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Copy the template files to the target directory.
|
|
212
|
-
*/
|
|
213
|
-
console.log("\nInitializing project with template:", template, "\n");
|
|
214
|
-
const templatePath = path.join(TEMPLATES_DIR, template, mode);
|
|
215
|
-
const copySource = ["**"];
|
|
216
|
-
if (!eslint) copySource.push("!eslintrc.json");
|
|
217
|
-
|
|
218
|
-
await copy(copySource, root, {
|
|
219
|
-
parents: true,
|
|
220
|
-
cwd: templatePath,
|
|
221
|
-
rename(name) {
|
|
222
|
-
switch (name) {
|
|
223
|
-
case "gitignore":
|
|
224
|
-
case "eslintrc.json": {
|
|
225
|
-
return `.${name}`;
|
|
226
|
-
}
|
|
227
|
-
// Stored as `env` so the monorepo `.gitignore` (`.env`) does not drop the template.
|
|
228
|
-
case "env": {
|
|
229
|
-
return ".env";
|
|
230
|
-
}
|
|
231
|
-
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
|
|
232
|
-
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
|
|
233
|
-
case "README-template.md": {
|
|
234
|
-
return "README.md";
|
|
235
|
-
}
|
|
236
|
-
case "zudoku.config.tsx": {
|
|
237
|
-
return "apitogo.config.tsx";
|
|
238
|
-
}
|
|
239
|
-
case "zudoku.config.jsx": {
|
|
240
|
-
return "apitogo.config.jsx";
|
|
241
|
-
}
|
|
242
|
-
default: {
|
|
243
|
-
return name;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
const appTitle = formatAppTitle(appName);
|
|
250
|
-
const substituteAppTitle = (contents: string) =>
|
|
251
|
-
contents.split("__APP_TITLE__").join(appTitle);
|
|
252
|
-
|
|
253
|
-
const configFilename =
|
|
254
|
-
mode === "ts" ? "apitogo.config.tsx" : "apitogo.config.jsx";
|
|
255
|
-
const configPath = path.join(root, configFilename);
|
|
256
|
-
const configContents = await fs.readFile(configPath, "utf8");
|
|
257
|
-
await fs.writeFile(configPath, substituteAppTitle(configContents), "utf8");
|
|
258
|
-
|
|
259
|
-
const jsonConfigPath = path.join(root, "apitogo.config.json");
|
|
260
|
-
try {
|
|
261
|
-
const jsonConfigContents = await fs.readFile(jsonConfigPath, "utf8");
|
|
262
|
-
await fs.writeFile(
|
|
263
|
-
jsonConfigPath,
|
|
264
|
-
substituteAppTitle(jsonConfigContents),
|
|
265
|
-
"utf8",
|
|
266
|
-
);
|
|
267
|
-
} catch {
|
|
268
|
-
// Optional in customized templates.
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// update import alias in any files if not using the default
|
|
272
|
-
|
|
273
|
-
/** Copy the version from package.json or override for tests. */
|
|
274
|
-
const version = process.env.ZUDOKU_PRIVATE_TEST_VERSION ?? zudokuVersion;
|
|
275
|
-
const localZudokuPackageDir = path.join(WORKSPACE_ROOT, "packages", "zudoku");
|
|
276
|
-
const localZudokuPackageJson = path.join(
|
|
277
|
-
localZudokuPackageDir,
|
|
278
|
-
"package.json",
|
|
279
|
-
);
|
|
280
|
-
const resolvedZudokuDependency = await fs
|
|
281
|
-
.access(localZudokuPackageJson)
|
|
282
|
-
.then(async () => {
|
|
283
|
-
const localPackageDir = path.join(root, ".apitogo-local");
|
|
284
|
-
const stagedPackageDir = path.join(localPackageDir, "zudoku-package");
|
|
285
|
-
await fs.mkdir(localPackageDir, { recursive: true });
|
|
286
|
-
await stageLocalZudokuPackage(localZudokuPackageDir, stagedPackageDir);
|
|
287
|
-
|
|
288
|
-
const packDestinationArg = path.relative(
|
|
289
|
-
stagedPackageDir,
|
|
290
|
-
localPackageDir,
|
|
291
|
-
);
|
|
292
|
-
const packOutput = (
|
|
293
|
-
process.platform === "win32"
|
|
294
|
-
? execFileSync(
|
|
295
|
-
WINDOWS_SHELL,
|
|
296
|
-
[
|
|
297
|
-
"/d",
|
|
298
|
-
"/c",
|
|
299
|
-
"npm",
|
|
300
|
-
"pack",
|
|
301
|
-
"--pack-destination",
|
|
302
|
-
packDestinationArg,
|
|
303
|
-
],
|
|
304
|
-
{ cwd: stagedPackageDir, encoding: "utf8" },
|
|
305
|
-
)
|
|
306
|
-
: execFileSync(
|
|
307
|
-
"npm",
|
|
308
|
-
["pack", "--pack-destination", packDestinationArg],
|
|
309
|
-
{
|
|
310
|
-
cwd: stagedPackageDir,
|
|
311
|
-
encoding: "utf8",
|
|
312
|
-
},
|
|
313
|
-
)
|
|
314
|
-
)
|
|
315
|
-
.trim()
|
|
316
|
-
.split(/\r?\n/)
|
|
317
|
-
.at(-1);
|
|
318
|
-
|
|
319
|
-
if (!packOutput) {
|
|
320
|
-
throw new Error("Failed to pack local zudoku package.");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return `file:.apitogo-local/${packOutput}`;
|
|
324
|
-
})
|
|
325
|
-
.catch(() => version);
|
|
326
|
-
|
|
327
|
-
const coreApitogoPackageName = await resolveCoreApitogoPackageName();
|
|
328
|
-
|
|
329
|
-
/** Create a package.json for the new project and write it to disk. */
|
|
330
|
-
// biome-ignore lint/suspicious/noExplicitAny: Allow any type
|
|
331
|
-
const packageJson: any = {
|
|
332
|
-
name: appName,
|
|
333
|
-
version: "0.1.0",
|
|
334
|
-
type: "module",
|
|
335
|
-
private: true,
|
|
336
|
-
scripts: {
|
|
337
|
-
dev: "apitogo run",
|
|
338
|
-
build: "apitogo build",
|
|
339
|
-
preview: "apitogo build --preview",
|
|
340
|
-
deploy: "apitogo deploy",
|
|
341
|
-
lint: "eslint",
|
|
342
|
-
},
|
|
343
|
-
/**
|
|
344
|
-
* Default dependencies.
|
|
345
|
-
*/
|
|
346
|
-
dependencies: {
|
|
347
|
-
react: ">=19.0.0",
|
|
348
|
-
"react-dom": ">=19.0.0",
|
|
349
|
-
[coreApitogoPackageName]: resolvedZudokuDependency,
|
|
350
|
-
},
|
|
351
|
-
devDependencies: {},
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
if (!resolvedZudokuDependency.startsWith("file:")) {
|
|
355
|
-
ensureRequiredApitogoModules(
|
|
356
|
-
packageJson.dependencies,
|
|
357
|
-
coreApitogoPackageName,
|
|
358
|
-
resolvedZudokuDependency,
|
|
359
|
-
);
|
|
360
|
-
ensureSecondaryApitogoAliases(
|
|
361
|
-
packageJson.dependencies,
|
|
362
|
-
coreApitogoPackageName,
|
|
363
|
-
resolvedZudokuDependency,
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* TypeScript projects will have type definitions and other devDependencies.
|
|
369
|
-
*/
|
|
370
|
-
if (mode === "ts") {
|
|
371
|
-
packageJson.devDependencies = {
|
|
372
|
-
...packageJson.devDependencies,
|
|
373
|
-
"@types/node": "^22",
|
|
374
|
-
"@types/react": "^19",
|
|
375
|
-
"@types/react-dom": "^19",
|
|
376
|
-
typescript: "^5",
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/* Default ESLint dependencies. */
|
|
381
|
-
if (eslint) {
|
|
382
|
-
packageJson.devDependencies = {
|
|
383
|
-
...packageJson.devDependencies,
|
|
384
|
-
eslint: "^8",
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Typescript eslint dependencies
|
|
389
|
-
if (eslint && mode === "ts") {
|
|
390
|
-
packageJson.devDependencies = {
|
|
391
|
-
...packageJson.devDependencies,
|
|
392
|
-
"@typescript-eslint/eslint-plugin": "^8",
|
|
393
|
-
"@typescript-eslint/parser": "^8",
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const devDeps = Object.keys(packageJson.devDependencies).length;
|
|
398
|
-
if (!devDeps) delete packageJson.devDependencies;
|
|
399
|
-
|
|
400
|
-
await fs.writeFile(
|
|
401
|
-
path.join(root, "package.json"),
|
|
402
|
-
JSON.stringify(packageJson, null, 2) + os.EOL,
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
if (skipInstall) return;
|
|
406
|
-
|
|
407
|
-
console.log("\nInstalling dependencies:");
|
|
408
|
-
for (const dependency in packageJson.dependencies)
|
|
409
|
-
console.log(`- ${cyan(dependency)}`);
|
|
410
|
-
|
|
411
|
-
if (devDeps) {
|
|
412
|
-
console.log("\nInstalling devDependencies:");
|
|
413
|
-
for (const dependency in packageJson.devDependencies)
|
|
414
|
-
console.log(`- ${cyan(dependency)}`);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
console.log();
|
|
418
|
-
|
|
419
|
-
await install(packageManager, isOnline);
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
export * from "./types";
|
|
1
|
+
// biome-ignore-all lint/suspicious/noConsole: Logging allowed here
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { bold, cyan } from "picocolors";
|
|
7
|
+
import { copy } from "../helpers/copy";
|
|
8
|
+
import { install } from "../helpers/install";
|
|
9
|
+
import type { GetTemplateFileArgs, InstallTemplateArgs } from "./types";
|
|
10
|
+
|
|
11
|
+
// When bundled by ncc, __dirname points to dist/, but templates are at dist/templates/
|
|
12
|
+
const PACKAGE_ROOT = __dirname.endsWith("dist")
|
|
13
|
+
? path.dirname(__dirname)
|
|
14
|
+
: path.dirname(__dirname);
|
|
15
|
+
const TEMPLATES_DIR = __dirname.endsWith("dist")
|
|
16
|
+
? path.join(__dirname, "templates")
|
|
17
|
+
: __dirname;
|
|
18
|
+
const WORKSPACE_ROOT = path.resolve(PACKAGE_ROOT, "../..");
|
|
19
|
+
const WINDOWS_SHELL = process.env.ComSpec ?? "cmd.exe";
|
|
20
|
+
|
|
21
|
+
const formatAppTitle = (appName: string) =>
|
|
22
|
+
appName
|
|
23
|
+
.split(/[-_\s]+/)
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
26
|
+
.join(" ");
|
|
27
|
+
|
|
28
|
+
const resolveCoreApitogoPackageName = async (): Promise<string> => {
|
|
29
|
+
const createPackageJson = JSON.parse(
|
|
30
|
+
await fs.readFile(path.join(PACKAGE_ROOT, "package.json"), "utf8"),
|
|
31
|
+
) as { name: string };
|
|
32
|
+
return createPackageJson.name.replace("/create-apitogo", "/apitogo");
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Primary npm scope; split so secondary publish does not rewrite this guard. */
|
|
36
|
+
const PRIMARY_APITOGO_SCOPE = "@lukow" + "eb";
|
|
37
|
+
|
|
38
|
+
const normalizeDependencyVersion = (version: string): string =>
|
|
39
|
+
version.replace(/^[\^~>=<]+/, "");
|
|
40
|
+
|
|
41
|
+
const LUCIDE_REACT_VERSION = "0.577.0";
|
|
42
|
+
|
|
43
|
+
const ensureRequiredApitogoModules = (
|
|
44
|
+
dependencies: Record<string, string>,
|
|
45
|
+
corePackageName: string,
|
|
46
|
+
version: string,
|
|
47
|
+
) => {
|
|
48
|
+
const scope = corePackageName.slice(0, corePackageName.indexOf("/"));
|
|
49
|
+
const landing = `${scope}/apitogo-module-landing`;
|
|
50
|
+
const userPanel = `${scope}/apitogo-module-user-panel`;
|
|
51
|
+
const billing = `${scope}/apitogo-plugin-dev-portal-billing`;
|
|
52
|
+
const moduleVersion = `^${normalizeDependencyVersion(version)}`;
|
|
53
|
+
|
|
54
|
+
dependencies[landing] ??= moduleVersion;
|
|
55
|
+
dependencies[userPanel] ??= moduleVersion;
|
|
56
|
+
dependencies[billing] ??= moduleVersion;
|
|
57
|
+
dependencies["lucide-react"] ??= LUCIDE_REACT_VERSION;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const ensureSecondaryApitogoAliases = (
|
|
61
|
+
dependencies: Record<string, string>,
|
|
62
|
+
corePackageName: string,
|
|
63
|
+
version: string,
|
|
64
|
+
) => {
|
|
65
|
+
if (corePackageName.startsWith(`${PRIMARY_APITOGO_SCOPE}/`)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const alias = (target: string) =>
|
|
70
|
+
`npm:${target}@${normalizeDependencyVersion(version)}`;
|
|
71
|
+
const scope = corePackageName.slice(0, corePackageName.indexOf("/"));
|
|
72
|
+
|
|
73
|
+
dependencies[`${PRIMARY_APITOGO_SCOPE}/apitogo`] = alias(corePackageName);
|
|
74
|
+
dependencies[`${PRIMARY_APITOGO_SCOPE}/apitogo-module-landing`] = alias(
|
|
75
|
+
`${scope}/apitogo-module-landing`,
|
|
76
|
+
);
|
|
77
|
+
dependencies[`${PRIMARY_APITOGO_SCOPE}/apitogo-module-user-panel`] = alias(
|
|
78
|
+
`${scope}/apitogo-module-user-panel`,
|
|
79
|
+
);
|
|
80
|
+
dependencies[`${PRIMARY_APITOGO_SCOPE}/apitogo-plugin-dev-portal-billing`] =
|
|
81
|
+
alias(`${scope}/apitogo-plugin-dev-portal-billing`);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const parseWorkspaceCatalog = async (workspaceFile: string) => {
|
|
85
|
+
const content = await fs.readFile(workspaceFile, "utf8");
|
|
86
|
+
const catalog: Record<string, string> = {};
|
|
87
|
+
let inCatalog = false;
|
|
88
|
+
|
|
89
|
+
for (const line of content.split(/\r?\n/)) {
|
|
90
|
+
if (!inCatalog) {
|
|
91
|
+
inCatalog = line.trim() === "catalog:";
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!line.startsWith(" ")) break;
|
|
96
|
+
|
|
97
|
+
const match = line.match(/^ {2}(?:"([^"]+)"|([^:]+)):\s+(.+)$/);
|
|
98
|
+
if (!match) continue;
|
|
99
|
+
|
|
100
|
+
const dependency = match[1] ?? match[2]?.trim();
|
|
101
|
+
const version = match[3]?.trim();
|
|
102
|
+
if (dependency && version) catalog[dependency] = version;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return catalog;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const replaceCatalogVersions = (
|
|
109
|
+
value: unknown,
|
|
110
|
+
catalog: Record<string, string>,
|
|
111
|
+
): unknown => {
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
return value.map((entry) => replaceCatalogVersions(entry, catalog));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!value || typeof value !== "object") {
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return Object.fromEntries(
|
|
121
|
+
Object.entries(value).map(([key, entry]) => [
|
|
122
|
+
key,
|
|
123
|
+
entry === "catalog:"
|
|
124
|
+
? (catalog[key] ?? entry)
|
|
125
|
+
: replaceCatalogVersions(entry, catalog),
|
|
126
|
+
]),
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const stageLocalZudokuPackage = async (
|
|
131
|
+
sourceDir: string,
|
|
132
|
+
stagingDir: string,
|
|
133
|
+
) => {
|
|
134
|
+
const workspaceCatalog = await parseWorkspaceCatalog(
|
|
135
|
+
path.join(WORKSPACE_ROOT, "pnpm-workspace.yaml"),
|
|
136
|
+
);
|
|
137
|
+
const packageJsonPath = path.join(sourceDir, "package.json");
|
|
138
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
139
|
+
const stagedPackageJson = replaceCatalogVersions(
|
|
140
|
+
packageJson,
|
|
141
|
+
workspaceCatalog,
|
|
142
|
+
) as typeof packageJson;
|
|
143
|
+
const packageFiles = new Set<string>([
|
|
144
|
+
"package.json",
|
|
145
|
+
...(Array.isArray(packageJson.files) ? packageJson.files : []),
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
await fs.rm(stagingDir, { recursive: true, force: true });
|
|
149
|
+
await fs.mkdir(stagingDir, { recursive: true });
|
|
150
|
+
|
|
151
|
+
for (const entry of packageFiles) {
|
|
152
|
+
if (entry === "package.json") continue;
|
|
153
|
+
|
|
154
|
+
const sourcePath = path.join(sourceDir, entry);
|
|
155
|
+
const destinationPath = path.join(stagingDir, entry);
|
|
156
|
+
const stat = await fs.stat(sourcePath).catch(() => null);
|
|
157
|
+
|
|
158
|
+
if (!stat) continue;
|
|
159
|
+
|
|
160
|
+
if (stat.isDirectory()) {
|
|
161
|
+
await fs.cp(sourcePath, destinationPath, { recursive: true });
|
|
162
|
+
} else {
|
|
163
|
+
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
164
|
+
await fs.copyFile(sourcePath, destinationPath);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
delete stagedPackageJson.scripts?.prepublishOnly;
|
|
169
|
+
delete stagedPackageJson.scripts?.postpublish;
|
|
170
|
+
if (
|
|
171
|
+
stagedPackageJson.scripts &&
|
|
172
|
+
Object.keys(stagedPackageJson.scripts).length === 0
|
|
173
|
+
) {
|
|
174
|
+
delete stagedPackageJson.scripts;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await fs.writeFile(
|
|
178
|
+
path.join(stagingDir, "package.json"),
|
|
179
|
+
JSON.stringify(stagedPackageJson, null, 2) + os.EOL,
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get the file path for a given file in a template, e.g. "next.config.js".
|
|
185
|
+
*/
|
|
186
|
+
export const getTemplateFile = ({
|
|
187
|
+
template,
|
|
188
|
+
mode,
|
|
189
|
+
file,
|
|
190
|
+
}: GetTemplateFileArgs): string => {
|
|
191
|
+
return path.join(TEMPLATES_DIR, template, mode, file);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Install a Zudoku internal template to a given `root` directory.
|
|
196
|
+
*/
|
|
197
|
+
export const installTemplate = async ({
|
|
198
|
+
appName,
|
|
199
|
+
root,
|
|
200
|
+
packageManager,
|
|
201
|
+
isOnline,
|
|
202
|
+
template,
|
|
203
|
+
mode,
|
|
204
|
+
eslint,
|
|
205
|
+
skipInstall,
|
|
206
|
+
zudokuVersion,
|
|
207
|
+
}: InstallTemplateArgs) => {
|
|
208
|
+
console.log(bold(`Using ${packageManager}.`));
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Copy the template files to the target directory.
|
|
212
|
+
*/
|
|
213
|
+
console.log("\nInitializing project with template:", template, "\n");
|
|
214
|
+
const templatePath = path.join(TEMPLATES_DIR, template, mode);
|
|
215
|
+
const copySource = ["**"];
|
|
216
|
+
if (!eslint) copySource.push("!eslintrc.json");
|
|
217
|
+
|
|
218
|
+
await copy(copySource, root, {
|
|
219
|
+
parents: true,
|
|
220
|
+
cwd: templatePath,
|
|
221
|
+
rename(name) {
|
|
222
|
+
switch (name) {
|
|
223
|
+
case "gitignore":
|
|
224
|
+
case "eslintrc.json": {
|
|
225
|
+
return `.${name}`;
|
|
226
|
+
}
|
|
227
|
+
// Stored as `env` so the monorepo `.gitignore` (`.env`) does not drop the template.
|
|
228
|
+
case "env": {
|
|
229
|
+
return ".env";
|
|
230
|
+
}
|
|
231
|
+
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
|
|
232
|
+
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
|
|
233
|
+
case "README-template.md": {
|
|
234
|
+
return "README.md";
|
|
235
|
+
}
|
|
236
|
+
case "zudoku.config.tsx": {
|
|
237
|
+
return "apitogo.config.tsx";
|
|
238
|
+
}
|
|
239
|
+
case "zudoku.config.jsx": {
|
|
240
|
+
return "apitogo.config.jsx";
|
|
241
|
+
}
|
|
242
|
+
default: {
|
|
243
|
+
return name;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const appTitle = formatAppTitle(appName);
|
|
250
|
+
const substituteAppTitle = (contents: string) =>
|
|
251
|
+
contents.split("__APP_TITLE__").join(appTitle);
|
|
252
|
+
|
|
253
|
+
const configFilename =
|
|
254
|
+
mode === "ts" ? "apitogo.config.tsx" : "apitogo.config.jsx";
|
|
255
|
+
const configPath = path.join(root, configFilename);
|
|
256
|
+
const configContents = await fs.readFile(configPath, "utf8");
|
|
257
|
+
await fs.writeFile(configPath, substituteAppTitle(configContents), "utf8");
|
|
258
|
+
|
|
259
|
+
const jsonConfigPath = path.join(root, "apitogo.config.json");
|
|
260
|
+
try {
|
|
261
|
+
const jsonConfigContents = await fs.readFile(jsonConfigPath, "utf8");
|
|
262
|
+
await fs.writeFile(
|
|
263
|
+
jsonConfigPath,
|
|
264
|
+
substituteAppTitle(jsonConfigContents),
|
|
265
|
+
"utf8",
|
|
266
|
+
);
|
|
267
|
+
} catch {
|
|
268
|
+
// Optional in customized templates.
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// update import alias in any files if not using the default
|
|
272
|
+
|
|
273
|
+
/** Copy the version from package.json or override for tests. */
|
|
274
|
+
const version = process.env.ZUDOKU_PRIVATE_TEST_VERSION ?? zudokuVersion;
|
|
275
|
+
const localZudokuPackageDir = path.join(WORKSPACE_ROOT, "packages", "zudoku");
|
|
276
|
+
const localZudokuPackageJson = path.join(
|
|
277
|
+
localZudokuPackageDir,
|
|
278
|
+
"package.json",
|
|
279
|
+
);
|
|
280
|
+
const resolvedZudokuDependency = await fs
|
|
281
|
+
.access(localZudokuPackageJson)
|
|
282
|
+
.then(async () => {
|
|
283
|
+
const localPackageDir = path.join(root, ".apitogo-local");
|
|
284
|
+
const stagedPackageDir = path.join(localPackageDir, "zudoku-package");
|
|
285
|
+
await fs.mkdir(localPackageDir, { recursive: true });
|
|
286
|
+
await stageLocalZudokuPackage(localZudokuPackageDir, stagedPackageDir);
|
|
287
|
+
|
|
288
|
+
const packDestinationArg = path.relative(
|
|
289
|
+
stagedPackageDir,
|
|
290
|
+
localPackageDir,
|
|
291
|
+
);
|
|
292
|
+
const packOutput = (
|
|
293
|
+
process.platform === "win32"
|
|
294
|
+
? execFileSync(
|
|
295
|
+
WINDOWS_SHELL,
|
|
296
|
+
[
|
|
297
|
+
"/d",
|
|
298
|
+
"/c",
|
|
299
|
+
"npm",
|
|
300
|
+
"pack",
|
|
301
|
+
"--pack-destination",
|
|
302
|
+
packDestinationArg,
|
|
303
|
+
],
|
|
304
|
+
{ cwd: stagedPackageDir, encoding: "utf8" },
|
|
305
|
+
)
|
|
306
|
+
: execFileSync(
|
|
307
|
+
"npm",
|
|
308
|
+
["pack", "--pack-destination", packDestinationArg],
|
|
309
|
+
{
|
|
310
|
+
cwd: stagedPackageDir,
|
|
311
|
+
encoding: "utf8",
|
|
312
|
+
},
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
.trim()
|
|
316
|
+
.split(/\r?\n/)
|
|
317
|
+
.at(-1);
|
|
318
|
+
|
|
319
|
+
if (!packOutput) {
|
|
320
|
+
throw new Error("Failed to pack local zudoku package.");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return `file:.apitogo-local/${packOutput}`;
|
|
324
|
+
})
|
|
325
|
+
.catch(() => version);
|
|
326
|
+
|
|
327
|
+
const coreApitogoPackageName = await resolveCoreApitogoPackageName();
|
|
328
|
+
|
|
329
|
+
/** Create a package.json for the new project and write it to disk. */
|
|
330
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow any type
|
|
331
|
+
const packageJson: any = {
|
|
332
|
+
name: appName,
|
|
333
|
+
version: "0.1.0",
|
|
334
|
+
type: "module",
|
|
335
|
+
private: true,
|
|
336
|
+
scripts: {
|
|
337
|
+
dev: "apitogo run",
|
|
338
|
+
build: "apitogo build",
|
|
339
|
+
preview: "apitogo build --preview",
|
|
340
|
+
deploy: "apitogo deploy",
|
|
341
|
+
lint: "eslint",
|
|
342
|
+
},
|
|
343
|
+
/**
|
|
344
|
+
* Default dependencies.
|
|
345
|
+
*/
|
|
346
|
+
dependencies: {
|
|
347
|
+
react: ">=19.0.0",
|
|
348
|
+
"react-dom": ">=19.0.0",
|
|
349
|
+
[coreApitogoPackageName]: resolvedZudokuDependency,
|
|
350
|
+
},
|
|
351
|
+
devDependencies: {},
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
if (!resolvedZudokuDependency.startsWith("file:")) {
|
|
355
|
+
ensureRequiredApitogoModules(
|
|
356
|
+
packageJson.dependencies,
|
|
357
|
+
coreApitogoPackageName,
|
|
358
|
+
resolvedZudokuDependency,
|
|
359
|
+
);
|
|
360
|
+
ensureSecondaryApitogoAliases(
|
|
361
|
+
packageJson.dependencies,
|
|
362
|
+
coreApitogoPackageName,
|
|
363
|
+
resolvedZudokuDependency,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* TypeScript projects will have type definitions and other devDependencies.
|
|
369
|
+
*/
|
|
370
|
+
if (mode === "ts") {
|
|
371
|
+
packageJson.devDependencies = {
|
|
372
|
+
...packageJson.devDependencies,
|
|
373
|
+
"@types/node": "^22",
|
|
374
|
+
"@types/react": "^19",
|
|
375
|
+
"@types/react-dom": "^19",
|
|
376
|
+
typescript: "^5",
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/* Default ESLint dependencies. */
|
|
381
|
+
if (eslint) {
|
|
382
|
+
packageJson.devDependencies = {
|
|
383
|
+
...packageJson.devDependencies,
|
|
384
|
+
eslint: "^8",
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Typescript eslint dependencies
|
|
389
|
+
if (eslint && mode === "ts") {
|
|
390
|
+
packageJson.devDependencies = {
|
|
391
|
+
...packageJson.devDependencies,
|
|
392
|
+
"@typescript-eslint/eslint-plugin": "^8",
|
|
393
|
+
"@typescript-eslint/parser": "^8",
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const devDeps = Object.keys(packageJson.devDependencies).length;
|
|
398
|
+
if (!devDeps) delete packageJson.devDependencies;
|
|
399
|
+
|
|
400
|
+
await fs.writeFile(
|
|
401
|
+
path.join(root, "package.json"),
|
|
402
|
+
JSON.stringify(packageJson, null, 2) + os.EOL,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
if (skipInstall) return;
|
|
406
|
+
|
|
407
|
+
console.log("\nInstalling dependencies:");
|
|
408
|
+
for (const dependency in packageJson.dependencies)
|
|
409
|
+
console.log(`- ${cyan(dependency)}`);
|
|
410
|
+
|
|
411
|
+
if (devDeps) {
|
|
412
|
+
console.log("\nInstalling devDependencies:");
|
|
413
|
+
for (const dependency in packageJson.devDependencies)
|
|
414
|
+
console.log(`- ${cyan(dependency)}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
console.log();
|
|
418
|
+
|
|
419
|
+
await install(packageManager, isOnline);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
export * from "./types";
|