@zntc/init 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/bin/zntc-init.mjs +22 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +792 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +603 -0
- package/dist/react-native.d.ts +22 -0
- package/dist/rspack.d.ts +24 -0
- package/dist/shared.d.ts +48 -0
- package/dist/vite.d.ts +17 -0
- package/dist/web.d.ts +24 -0
- package/package.json +54 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { PACKAGE_MANAGERS, detectPackageManager, type FileAction, type FileChange, type PackageManager, type PlannedFile, } from './shared.ts';
|
|
2
|
+
export { DEFAULT_RN_ENTRY, DEFAULT_RN_PLATFORM, createReactNativeConfig, initReactNativeProject, planReactNativeInit, type InitReactNativeOptions, type InitReactNativeResult, type ReactNativePlatform, } from './react-native.ts';
|
|
3
|
+
export { createViteConfig, initViteProject, planViteInit, type InitViteOptions, type InitViteResult, } from './vite.ts';
|
|
4
|
+
export { createRspackConfig, initRspackProject, planRspackInit, type InitRspackOptions, type InitRspackResult, type RspackBundler, } from './rspack.ts';
|
|
5
|
+
export { WEB_FRAMEWORKS, initWebProject, planWebInit, type InitWebOptions, type InitWebResult, type WebFramework, } from './web.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
// src/shared.ts
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
var PACKAGE_MANAGERS = ["bun", "npm", "pnpm", "yarn"];
|
|
5
|
+
var PACKAGE_JSON = "package.json";
|
|
6
|
+
var ZNTC_CONFIG = "zntc.config.ts";
|
|
7
|
+
var DEFAULT_ZNTC_VERSION = "latest";
|
|
8
|
+
function readText(path) {
|
|
9
|
+
try {
|
|
10
|
+
return readFileSync(path, "utf8");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error.code === "ENOENT")
|
|
13
|
+
return null;
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function formatJson(value) {
|
|
18
|
+
return `${JSON.stringify(value, null, 2)}
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
function parsePackageJson(raw) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(raw);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(`failed to parse ${PACKAGE_JSON}: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function ensureObject(value) {
|
|
29
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
function hasAnyDependency(pkg, names) {
|
|
35
|
+
const deps = ensureObject(pkg.dependencies);
|
|
36
|
+
const dev = ensureObject(pkg.devDependencies);
|
|
37
|
+
const peer = ensureObject(pkg.peerDependencies);
|
|
38
|
+
return names.some((name) => Boolean(deps[name] ?? dev[name] ?? peer[name]));
|
|
39
|
+
}
|
|
40
|
+
function addDevDependency(pkg, name, version) {
|
|
41
|
+
const deps = ensureObject(pkg.dependencies);
|
|
42
|
+
const dev = { ...ensureObject(pkg.devDependencies) };
|
|
43
|
+
if (deps[name] || dev[name])
|
|
44
|
+
return pkg;
|
|
45
|
+
dev[name] = version;
|
|
46
|
+
return { ...pkg, devDependencies: dev };
|
|
47
|
+
}
|
|
48
|
+
function toChange(file) {
|
|
49
|
+
if (file.manualInstructions && !file.changed) {
|
|
50
|
+
return { path: file.path, action: "manual", manualInstructions: file.manualInstructions };
|
|
51
|
+
}
|
|
52
|
+
if (!file.changed)
|
|
53
|
+
return { path: file.path, action: "unchanged" };
|
|
54
|
+
return { path: file.path, action: file.before === null ? "create" : "update" };
|
|
55
|
+
}
|
|
56
|
+
function detectPackageManager(root) {
|
|
57
|
+
if (existsSync(join(root, "bun.lock")) || existsSync(join(root, "bun.lockb")))
|
|
58
|
+
return "bun";
|
|
59
|
+
if (existsSync(join(root, "pnpm-lock.yaml")))
|
|
60
|
+
return "pnpm";
|
|
61
|
+
if (existsSync(join(root, "yarn.lock")))
|
|
62
|
+
return "yarn";
|
|
63
|
+
const rawPackageJson = readText(join(root, PACKAGE_JSON));
|
|
64
|
+
if (rawPackageJson) {
|
|
65
|
+
try {
|
|
66
|
+
const packageManager = JSON.parse(rawPackageJson).packageManager;
|
|
67
|
+
if (typeof packageManager === "string") {
|
|
68
|
+
for (const pm of PACKAGE_MANAGERS) {
|
|
69
|
+
if (packageManager.startsWith(`${pm}@`))
|
|
70
|
+
return pm;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
return "npm";
|
|
76
|
+
}
|
|
77
|
+
function installCommand(pm) {
|
|
78
|
+
return `${pm} install`;
|
|
79
|
+
}
|
|
80
|
+
function applyPlan(opts) {
|
|
81
|
+
if (!opts.dryRun) {
|
|
82
|
+
for (const file of opts.planned) {
|
|
83
|
+
if (!file.changed)
|
|
84
|
+
continue;
|
|
85
|
+
mkdirSync(dirname(file.path), { recursive: true });
|
|
86
|
+
writeFileSync(file.path, file.after);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const pm = opts.packageManager ?? detectPackageManager(opts.root);
|
|
90
|
+
return {
|
|
91
|
+
root: opts.root,
|
|
92
|
+
changes: opts.planned.map(toChange),
|
|
93
|
+
dryRun: opts.dryRun,
|
|
94
|
+
installCommand: installCommand(pm)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function planOverlayConfig(opts) {
|
|
98
|
+
for (const name of opts.candidates) {
|
|
99
|
+
const path2 = join(opts.root, name);
|
|
100
|
+
const before = readText(path2);
|
|
101
|
+
if (before === null)
|
|
102
|
+
continue;
|
|
103
|
+
if (!opts.force) {
|
|
104
|
+
return {
|
|
105
|
+
path: path2,
|
|
106
|
+
before,
|
|
107
|
+
after: before,
|
|
108
|
+
changed: false,
|
|
109
|
+
manualInstructions: opts.manualInstructions
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const after = opts.generate();
|
|
113
|
+
return { path: path2, before, after, changed: before !== after };
|
|
114
|
+
}
|
|
115
|
+
const path = join(opts.root, opts.defaultName);
|
|
116
|
+
return { path, before: null, after: opts.generate(), changed: true };
|
|
117
|
+
}
|
|
118
|
+
// src/react-native.ts
|
|
119
|
+
import { join as join2, resolve } from "node:path";
|
|
120
|
+
var DEFAULT_RN_ENTRY = "index.js";
|
|
121
|
+
var DEFAULT_RN_PLATFORM = "ios";
|
|
122
|
+
function bundleOut(platform) {
|
|
123
|
+
return platform === "ios" ? "--bundle-output ios/main.jsbundle --assets-dest ios" : "--bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res";
|
|
124
|
+
}
|
|
125
|
+
function zntcBundleScript(platform) {
|
|
126
|
+
return `zntc --bundle ${DEFAULT_RN_ENTRY} --platform=react-native --rn-platform=${platform} --minify ${bundleOut(platform)}`;
|
|
127
|
+
}
|
|
128
|
+
function metroBundleScript(platform) {
|
|
129
|
+
return `react-native bundle --platform ${platform} --dev false --entry-file ${DEFAULT_RN_ENTRY} ${bundleOut(platform)}`;
|
|
130
|
+
}
|
|
131
|
+
function patchPackageJson(pkg, options) {
|
|
132
|
+
let next = addDevDependency(pkg, "@zntc/core", options.zntcVersion);
|
|
133
|
+
next = addDevDependency(next, "@zntc/react-native", options.zntcVersion);
|
|
134
|
+
const scripts = { ...ensureObject(next.scripts) };
|
|
135
|
+
const zntcStart = `zntc dev --platform=react-native --rn-platform=${options.defaultPlatform} ${DEFAULT_RN_ENTRY}`;
|
|
136
|
+
const previousStart = typeof scripts.start === "string" ? scripts.start : undefined;
|
|
137
|
+
if (options.metroFallback && previousStart && previousStart !== zntcStart) {
|
|
138
|
+
scripts["start:metro"] = scripts["start:metro"] ?? previousStart;
|
|
139
|
+
}
|
|
140
|
+
scripts.start = zntcStart;
|
|
141
|
+
scripts["start:zntc"] = scripts["start:zntc"] ?? zntcStart;
|
|
142
|
+
scripts["bundle:ios"] = scripts["bundle:ios"] ?? zntcBundleScript("ios");
|
|
143
|
+
scripts["bundle:android"] = scripts["bundle:android"] ?? zntcBundleScript("android");
|
|
144
|
+
if (options.metroFallback) {
|
|
145
|
+
scripts["bundle:metro:ios"] = scripts["bundle:metro:ios"] ?? metroBundleScript("ios");
|
|
146
|
+
scripts["bundle:metro:android"] = scripts["bundle:metro:android"] ?? metroBundleScript("android");
|
|
147
|
+
}
|
|
148
|
+
return { ...next, scripts };
|
|
149
|
+
}
|
|
150
|
+
function createReactNativeConfig() {
|
|
151
|
+
return `// ZNTC dev server + bundler config for React Native CLI.
|
|
152
|
+
// Generated by @zntc/init.
|
|
153
|
+
|
|
154
|
+
import { dirname } from "node:path";
|
|
155
|
+
import { fileURLToPath } from "node:url";
|
|
156
|
+
|
|
157
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
158
|
+
const __dirname = dirname(__filename);
|
|
159
|
+
|
|
160
|
+
export default {
|
|
161
|
+
root: __dirname,
|
|
162
|
+
entry: "${DEFAULT_RN_ENTRY}",
|
|
163
|
+
dev: true,
|
|
164
|
+
minify: false,
|
|
165
|
+
transformer: {
|
|
166
|
+
babel: {},
|
|
167
|
+
},
|
|
168
|
+
serializer: {
|
|
169
|
+
polyfills: [],
|
|
170
|
+
prelude: [],
|
|
171
|
+
},
|
|
172
|
+
server: {
|
|
173
|
+
port: 8081,
|
|
174
|
+
host: "localhost",
|
|
175
|
+
useGlobalHotkey: true,
|
|
176
|
+
forwardClientLogs: true,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
function planPackageJson(root, options) {
|
|
182
|
+
const path = join2(root, PACKAGE_JSON);
|
|
183
|
+
const before = readText(path);
|
|
184
|
+
if (before === null) {
|
|
185
|
+
throw new Error(`package.json not found in ${root}`);
|
|
186
|
+
}
|
|
187
|
+
const pkg = parsePackageJson(before);
|
|
188
|
+
if (!hasAnyDependency(pkg, ["react-native"])) {
|
|
189
|
+
throw new Error("react-native dependency not found; use `zntc-init vite|rspack|web` for non-RN projects");
|
|
190
|
+
}
|
|
191
|
+
const after = formatJson(patchPackageJson(pkg, options));
|
|
192
|
+
return { path, before, after, changed: before !== after };
|
|
193
|
+
}
|
|
194
|
+
function planConfig(root, force) {
|
|
195
|
+
const path = join2(root, ZNTC_CONFIG);
|
|
196
|
+
const before = readText(path);
|
|
197
|
+
const after = before !== null && !force ? before : createReactNativeConfig();
|
|
198
|
+
return { path, before, after, changed: before !== after };
|
|
199
|
+
}
|
|
200
|
+
function planReactNativeInit(options = {}) {
|
|
201
|
+
const root = resolve(options.root ?? process.cwd());
|
|
202
|
+
const force = options.force ?? false;
|
|
203
|
+
const normalized = {
|
|
204
|
+
defaultPlatform: options.defaultPlatform ?? DEFAULT_RN_PLATFORM,
|
|
205
|
+
metroFallback: options.metroFallback ?? true,
|
|
206
|
+
zntcVersion: options.zntcVersion ?? DEFAULT_ZNTC_VERSION
|
|
207
|
+
};
|
|
208
|
+
if (normalized.defaultPlatform !== "ios" && normalized.defaultPlatform !== "android") {
|
|
209
|
+
throw new Error(`unsupported default platform: ${normalized.defaultPlatform}`);
|
|
210
|
+
}
|
|
211
|
+
return [planPackageJson(root, normalized), planConfig(root, force)];
|
|
212
|
+
}
|
|
213
|
+
function initReactNativeProject(options = {}) {
|
|
214
|
+
const root = resolve(options.root ?? process.cwd());
|
|
215
|
+
const planned = planReactNativeInit({ ...options, root });
|
|
216
|
+
return applyPlan({
|
|
217
|
+
root,
|
|
218
|
+
planned,
|
|
219
|
+
dryRun: options.dryRun ?? false,
|
|
220
|
+
packageManager: options.packageManager
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
// src/vite.ts
|
|
224
|
+
import { join as join3, resolve as resolve2 } from "node:path";
|
|
225
|
+
var VITE_CONFIG_CANDIDATES = [
|
|
226
|
+
"vite.config.ts",
|
|
227
|
+
"vite.config.mts",
|
|
228
|
+
"vite.config.js",
|
|
229
|
+
"vite.config.mjs"
|
|
230
|
+
];
|
|
231
|
+
var DEFAULT_VITE_CONFIG = "vite.config.ts";
|
|
232
|
+
function createViteConfig() {
|
|
233
|
+
return `// Generated by @zntc/init — replaces Vite의 esbuild transform 을 ZNTC 로.
|
|
234
|
+
import { defineConfig } from 'vite';
|
|
235
|
+
import { zntc } from '@zntc/vite-plugin';
|
|
236
|
+
|
|
237
|
+
export default defineConfig({
|
|
238
|
+
plugins: [zntc()],
|
|
239
|
+
esbuild: false,
|
|
240
|
+
});
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
function planPackageJson2(root, zntcVersion) {
|
|
244
|
+
const path = join3(root, PACKAGE_JSON);
|
|
245
|
+
const before = readText(path);
|
|
246
|
+
if (before === null) {
|
|
247
|
+
throw new Error(`package.json not found in ${root}`);
|
|
248
|
+
}
|
|
249
|
+
const pkg = parsePackageJson(before);
|
|
250
|
+
if (!hasAnyDependency(pkg, ["vite"])) {
|
|
251
|
+
throw new Error("vite dependency not found; install Vite first, or use `zntc-init web` for a standalone setup");
|
|
252
|
+
}
|
|
253
|
+
let next = addDevDependency(pkg, "@zntc/core", zntcVersion);
|
|
254
|
+
next = addDevDependency(next, "@zntc/vite-plugin", zntcVersion);
|
|
255
|
+
const after = formatJson(next);
|
|
256
|
+
return { path, before, after, changed: before !== after };
|
|
257
|
+
}
|
|
258
|
+
var MANUAL_PATCH = [
|
|
259
|
+
"Add ZNTC to your existing vite.config:",
|
|
260
|
+
"",
|
|
261
|
+
" import { zntc } from '@zntc/vite-plugin';",
|
|
262
|
+
"",
|
|
263
|
+
" export default defineConfig({",
|
|
264
|
+
" plugins: [zntc()], // 기존 plugins 배열에 합쳐 주세요",
|
|
265
|
+
" esbuild: false, // ZNTC 가 .ts/.tsx/.jsx 변환을 담당",
|
|
266
|
+
" });",
|
|
267
|
+
"",
|
|
268
|
+
"Re-run with --force to overwrite the existing config instead."
|
|
269
|
+
].join(`
|
|
270
|
+
`);
|
|
271
|
+
function planViteInit(options = {}) {
|
|
272
|
+
const root = resolve2(options.root ?? process.cwd());
|
|
273
|
+
const zntcVersion = options.zntcVersion ?? DEFAULT_ZNTC_VERSION;
|
|
274
|
+
const force = options.force ?? false;
|
|
275
|
+
return [
|
|
276
|
+
planPackageJson2(root, zntcVersion),
|
|
277
|
+
planOverlayConfig({
|
|
278
|
+
root,
|
|
279
|
+
candidates: VITE_CONFIG_CANDIDATES,
|
|
280
|
+
defaultName: DEFAULT_VITE_CONFIG,
|
|
281
|
+
generate: createViteConfig,
|
|
282
|
+
manualInstructions: MANUAL_PATCH,
|
|
283
|
+
force
|
|
284
|
+
})
|
|
285
|
+
];
|
|
286
|
+
}
|
|
287
|
+
function initViteProject(options = {}) {
|
|
288
|
+
const root = resolve2(options.root ?? process.cwd());
|
|
289
|
+
const planned = planViteInit({ ...options, root });
|
|
290
|
+
return applyPlan({
|
|
291
|
+
root,
|
|
292
|
+
planned,
|
|
293
|
+
dryRun: options.dryRun ?? false,
|
|
294
|
+
packageManager: options.packageManager
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
// src/rspack.ts
|
|
298
|
+
import { join as join4, resolve as resolve3 } from "node:path";
|
|
299
|
+
var CONFIG_CANDIDATES = {
|
|
300
|
+
rspack: ["rspack.config.ts", "rspack.config.mts", "rspack.config.js", "rspack.config.mjs"],
|
|
301
|
+
webpack: ["webpack.config.ts", "webpack.config.mts", "webpack.config.js", "webpack.config.mjs"]
|
|
302
|
+
};
|
|
303
|
+
var DEFAULT_CONFIG_NAME = {
|
|
304
|
+
rspack: "rspack.config.mjs",
|
|
305
|
+
webpack: "webpack.config.mjs"
|
|
306
|
+
};
|
|
307
|
+
function createRspackConfig(bundler) {
|
|
308
|
+
const header = bundler === "rspack" ? "// Generated by @zntc/init — Rspack with @zntc/rspack-loader (replaces swc-loader / esbuild-loader)." : "// Generated by @zntc/init — Webpack 5 with @zntc/rspack-loader (replaces babel-loader / swc-loader).";
|
|
309
|
+
return `${header}
|
|
310
|
+
export default {
|
|
311
|
+
module: {
|
|
312
|
+
rules: [
|
|
313
|
+
{
|
|
314
|
+
test: /\\.(?:tsx?|jsx?)$/,
|
|
315
|
+
exclude: /node_modules/,
|
|
316
|
+
loader: '@zntc/rspack-loader',
|
|
317
|
+
options: {
|
|
318
|
+
transpileOptions: { target: 'es2020', jsx: 'automatic' },
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
`;
|
|
325
|
+
}
|
|
326
|
+
function detectBundler(pkg, hint) {
|
|
327
|
+
if (hint)
|
|
328
|
+
return hint;
|
|
329
|
+
if (hasAnyDependency(pkg, ["@rspack/core", "@rspack/cli"]))
|
|
330
|
+
return "rspack";
|
|
331
|
+
if (hasAnyDependency(pkg, ["webpack", "webpack-cli"]))
|
|
332
|
+
return "webpack";
|
|
333
|
+
throw new Error("@rspack/core or webpack dependency not found; install one of them first or pass --bundler");
|
|
334
|
+
}
|
|
335
|
+
function planPackageJson3(root, zntcVersion, hint) {
|
|
336
|
+
const path = join4(root, PACKAGE_JSON);
|
|
337
|
+
const before = readText(path);
|
|
338
|
+
if (before === null) {
|
|
339
|
+
throw new Error(`package.json not found in ${root}`);
|
|
340
|
+
}
|
|
341
|
+
const pkg = parsePackageJson(before);
|
|
342
|
+
const bundler = detectBundler(pkg, hint);
|
|
343
|
+
let next = addDevDependency(pkg, "@zntc/core", zntcVersion);
|
|
344
|
+
next = addDevDependency(next, "@zntc/rspack-loader", zntcVersion);
|
|
345
|
+
const after = formatJson(next);
|
|
346
|
+
return { plan: { path, before, after, changed: before !== after }, bundler };
|
|
347
|
+
}
|
|
348
|
+
function manualPatch(bundler) {
|
|
349
|
+
const product = bundler === "rspack" ? "rspack.config" : "webpack.config";
|
|
350
|
+
return [
|
|
351
|
+
`Add the ZNTC loader to your existing ${product}:`,
|
|
352
|
+
"",
|
|
353
|
+
" module: {",
|
|
354
|
+
" rules: [",
|
|
355
|
+
" {",
|
|
356
|
+
" test: /\\.(?:tsx?|jsx?)$/,",
|
|
357
|
+
" exclude: /node_modules/,",
|
|
358
|
+
" loader: '@zntc/rspack-loader',",
|
|
359
|
+
" options: { transpileOptions: { target: 'es2020', jsx: 'automatic' } },",
|
|
360
|
+
" },",
|
|
361
|
+
" ],",
|
|
362
|
+
" },",
|
|
363
|
+
"",
|
|
364
|
+
"Re-run with --force to overwrite the existing config instead."
|
|
365
|
+
].join(`
|
|
366
|
+
`);
|
|
367
|
+
}
|
|
368
|
+
function planRspackInit(options = {}) {
|
|
369
|
+
const root = resolve3(options.root ?? process.cwd());
|
|
370
|
+
const zntcVersion = options.zntcVersion ?? DEFAULT_ZNTC_VERSION;
|
|
371
|
+
const force = options.force ?? false;
|
|
372
|
+
const { plan: pkgPlan, bundler } = planPackageJson3(root, zntcVersion, options.bundler);
|
|
373
|
+
const configPlan = planOverlayConfig({
|
|
374
|
+
root,
|
|
375
|
+
candidates: CONFIG_CANDIDATES[bundler],
|
|
376
|
+
defaultName: DEFAULT_CONFIG_NAME[bundler],
|
|
377
|
+
generate: () => createRspackConfig(bundler),
|
|
378
|
+
manualInstructions: manualPatch(bundler),
|
|
379
|
+
force
|
|
380
|
+
});
|
|
381
|
+
return { files: [pkgPlan, configPlan], bundler };
|
|
382
|
+
}
|
|
383
|
+
function initRspackProject(options = {}) {
|
|
384
|
+
const root = resolve3(options.root ?? process.cwd());
|
|
385
|
+
const { files: planned, bundler } = planRspackInit({ ...options, root });
|
|
386
|
+
const applied = applyPlan({
|
|
387
|
+
root,
|
|
388
|
+
planned,
|
|
389
|
+
dryRun: options.dryRun ?? false,
|
|
390
|
+
packageManager: options.packageManager
|
|
391
|
+
});
|
|
392
|
+
return { ...applied, bundler };
|
|
393
|
+
}
|
|
394
|
+
// src/web.ts
|
|
395
|
+
import { basename, join as join5, resolve as resolve4 } from "node:path";
|
|
396
|
+
var WEB_FRAMEWORKS = ["react", "vanilla"];
|
|
397
|
+
var REACT_DEPS = {
|
|
398
|
+
react: "^19.0.0",
|
|
399
|
+
"react-dom": "^19.0.0"
|
|
400
|
+
};
|
|
401
|
+
var REACT_DEV_DEPS = {
|
|
402
|
+
"@types/react": "^19.0.0",
|
|
403
|
+
"@types/react-dom": "^19.0.0",
|
|
404
|
+
typescript: "^5.6.0"
|
|
405
|
+
};
|
|
406
|
+
var VANILLA_DEV_DEPS = {
|
|
407
|
+
typescript: "^5.6.0"
|
|
408
|
+
};
|
|
409
|
+
function entryFile(framework) {
|
|
410
|
+
return framework === "react" ? "src/main.tsx" : "src/main.ts";
|
|
411
|
+
}
|
|
412
|
+
function projectName(root, name) {
|
|
413
|
+
return name ?? basename(root);
|
|
414
|
+
}
|
|
415
|
+
function createPackageJson(root, framework, options) {
|
|
416
|
+
const pkg = {
|
|
417
|
+
name: projectName(root, options.name),
|
|
418
|
+
version: "0.0.0",
|
|
419
|
+
private: true,
|
|
420
|
+
type: "module",
|
|
421
|
+
scripts: {
|
|
422
|
+
dev: "zntc dev",
|
|
423
|
+
build: "zntc build",
|
|
424
|
+
preview: "zntc preview"
|
|
425
|
+
},
|
|
426
|
+
devDependencies: {
|
|
427
|
+
"@zntc/core": options.zntcVersion
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
if (framework === "react") {
|
|
431
|
+
pkg.dependencies = { ...REACT_DEPS };
|
|
432
|
+
pkg.devDependencies = { ...pkg.devDependencies, ...REACT_DEV_DEPS };
|
|
433
|
+
} else {
|
|
434
|
+
pkg.devDependencies = { ...pkg.devDependencies, ...VANILLA_DEV_DEPS };
|
|
435
|
+
}
|
|
436
|
+
return formatJson(pkg);
|
|
437
|
+
}
|
|
438
|
+
function createTsconfig(framework) {
|
|
439
|
+
const compilerOptions = {
|
|
440
|
+
target: "ES2022",
|
|
441
|
+
module: "ESNext",
|
|
442
|
+
moduleResolution: "Bundler",
|
|
443
|
+
lib: ["ES2022", "DOM", "DOM.Iterable"],
|
|
444
|
+
strict: true,
|
|
445
|
+
skipLibCheck: true,
|
|
446
|
+
noEmit: true,
|
|
447
|
+
isolatedModules: true,
|
|
448
|
+
esModuleInterop: true,
|
|
449
|
+
resolveJsonModule: true,
|
|
450
|
+
allowSyntheticDefaultImports: true
|
|
451
|
+
};
|
|
452
|
+
if (framework === "react")
|
|
453
|
+
compilerOptions.jsx = "react-jsx";
|
|
454
|
+
return formatJson({
|
|
455
|
+
compilerOptions,
|
|
456
|
+
include: ["src/**/*", "zntc.config.ts"]
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function createZntcConfig(framework) {
|
|
460
|
+
const entry = entryFile(framework);
|
|
461
|
+
const compilerLine = framework === "react" ? `
|
|
462
|
+
// React 17+ automatic JSX runtime — @zntc/core 가 _jsx import 자동 삽입.` : "";
|
|
463
|
+
return `import { defineConfig } from "@zntc/core";
|
|
464
|
+
|
|
465
|
+
export default defineConfig({
|
|
466
|
+
entryPoints: ["${entry}"],
|
|
467
|
+
outdir: "dist",
|
|
468
|
+
format: "esm",
|
|
469
|
+
platform: "browser",
|
|
470
|
+
target: "es2022",${framework === "react" ? `
|
|
471
|
+
jsx: "automatic",` : ""}
|
|
472
|
+
sourcemap: true,${compilerLine}
|
|
473
|
+
});
|
|
474
|
+
`;
|
|
475
|
+
}
|
|
476
|
+
function createIndexHtml(framework, name) {
|
|
477
|
+
const entry = `/${entryFile(framework)}`;
|
|
478
|
+
const body = framework === "react" ? ` <div id="root"></div>
|
|
479
|
+
` : ` <div id="app"></div>
|
|
480
|
+
`;
|
|
481
|
+
return `<!DOCTYPE html>
|
|
482
|
+
<html lang="en">
|
|
483
|
+
<head>
|
|
484
|
+
<meta charset="UTF-8" />
|
|
485
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
486
|
+
<title>${name}</title>
|
|
487
|
+
</head>
|
|
488
|
+
<body>
|
|
489
|
+
${body} <script type="module" src="${entry}"></script>
|
|
490
|
+
</body>
|
|
491
|
+
</html>
|
|
492
|
+
`;
|
|
493
|
+
}
|
|
494
|
+
function createReactEntry() {
|
|
495
|
+
return `import { StrictMode } from "react";
|
|
496
|
+
import { createRoot } from "react-dom/client";
|
|
497
|
+
|
|
498
|
+
import { App } from "./App";
|
|
499
|
+
|
|
500
|
+
const root = document.getElementById("root");
|
|
501
|
+
if (!root) throw new Error("#root not found");
|
|
502
|
+
|
|
503
|
+
createRoot(root).render(
|
|
504
|
+
<StrictMode>
|
|
505
|
+
<App />
|
|
506
|
+
</StrictMode>,
|
|
507
|
+
);
|
|
508
|
+
`;
|
|
509
|
+
}
|
|
510
|
+
function createReactApp() {
|
|
511
|
+
return `export function App() {
|
|
512
|
+
return (
|
|
513
|
+
<main>
|
|
514
|
+
<h1>ZNTC + React</h1>
|
|
515
|
+
<p>
|
|
516
|
+
Edit <code>src/App.tsx</code> and save — HMR will reflect changes instantly.
|
|
517
|
+
</p>
|
|
518
|
+
</main>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
`;
|
|
522
|
+
}
|
|
523
|
+
function createVanillaEntry() {
|
|
524
|
+
return `const app = document.getElementById("app");
|
|
525
|
+
if (!app) throw new Error("#app not found");
|
|
526
|
+
|
|
527
|
+
app.innerHTML = \`
|
|
528
|
+
<main>
|
|
529
|
+
<h1>ZNTC web starter</h1>
|
|
530
|
+
<p>Edit <code>src/main.ts</code> and save — HMR will reflect changes instantly.</p>
|
|
531
|
+
</main>
|
|
532
|
+
\`;
|
|
533
|
+
`;
|
|
534
|
+
}
|
|
535
|
+
function scaffoldFiles(root, framework, zntcVersion, name) {
|
|
536
|
+
const files = [
|
|
537
|
+
{ relPath: PACKAGE_JSON, content: createPackageJson(root, framework, { name, zntcVersion }) },
|
|
538
|
+
{ relPath: "tsconfig.json", content: createTsconfig(framework) },
|
|
539
|
+
{ relPath: "zntc.config.ts", content: createZntcConfig(framework) },
|
|
540
|
+
{ relPath: "index.html", content: createIndexHtml(framework, projectName(root, name)) }
|
|
541
|
+
];
|
|
542
|
+
if (framework === "react") {
|
|
543
|
+
files.push({ relPath: "src/main.tsx", content: createReactEntry() });
|
|
544
|
+
files.push({ relPath: "src/App.tsx", content: createReactApp() });
|
|
545
|
+
} else {
|
|
546
|
+
files.push({ relPath: "src/main.ts", content: createVanillaEntry() });
|
|
547
|
+
}
|
|
548
|
+
return files;
|
|
549
|
+
}
|
|
550
|
+
function planScaffoldFile(root, file, force) {
|
|
551
|
+
const path = join5(root, file.relPath);
|
|
552
|
+
const before = readText(path);
|
|
553
|
+
if (before === null)
|
|
554
|
+
return { path, before, after: file.content, changed: true };
|
|
555
|
+
if (force && before !== file.content) {
|
|
556
|
+
return { path, before, after: file.content, changed: true };
|
|
557
|
+
}
|
|
558
|
+
return { path, before, after: before, changed: false };
|
|
559
|
+
}
|
|
560
|
+
function planWebInit(options = {}) {
|
|
561
|
+
const root = resolve4(options.root ?? process.cwd());
|
|
562
|
+
const framework = options.framework ?? "react";
|
|
563
|
+
if (!WEB_FRAMEWORKS.includes(framework)) {
|
|
564
|
+
throw new Error(`unsupported framework: ${framework}`);
|
|
565
|
+
}
|
|
566
|
+
const force = options.force ?? false;
|
|
567
|
+
const zntcVersion = options.zntcVersion ?? DEFAULT_ZNTC_VERSION;
|
|
568
|
+
const files = scaffoldFiles(root, framework, zntcVersion, options.name);
|
|
569
|
+
const existingPkg = readText(join5(root, PACKAGE_JSON));
|
|
570
|
+
if (existingPkg !== null && !force) {
|
|
571
|
+
throw new Error(`package.json already exists in ${root}; re-run with --force to overwrite (this is a scaffold mode)`);
|
|
572
|
+
}
|
|
573
|
+
return { files: files.map((f) => planScaffoldFile(root, f, force)), framework };
|
|
574
|
+
}
|
|
575
|
+
function initWebProject(options = {}) {
|
|
576
|
+
const root = resolve4(options.root ?? process.cwd());
|
|
577
|
+
const { files: planned, framework } = planWebInit({ ...options, root });
|
|
578
|
+
const applied = applyPlan({
|
|
579
|
+
root,
|
|
580
|
+
planned,
|
|
581
|
+
dryRun: options.dryRun ?? false,
|
|
582
|
+
packageManager: options.packageManager
|
|
583
|
+
});
|
|
584
|
+
return { ...applied, framework };
|
|
585
|
+
}
|
|
586
|
+
export {
|
|
587
|
+
planWebInit,
|
|
588
|
+
planViteInit,
|
|
589
|
+
planRspackInit,
|
|
590
|
+
planReactNativeInit,
|
|
591
|
+
initWebProject,
|
|
592
|
+
initViteProject,
|
|
593
|
+
initRspackProject,
|
|
594
|
+
initReactNativeProject,
|
|
595
|
+
detectPackageManager,
|
|
596
|
+
createViteConfig,
|
|
597
|
+
createRspackConfig,
|
|
598
|
+
createReactNativeConfig,
|
|
599
|
+
WEB_FRAMEWORKS,
|
|
600
|
+
PACKAGE_MANAGERS,
|
|
601
|
+
DEFAULT_RN_PLATFORM,
|
|
602
|
+
DEFAULT_RN_ENTRY
|
|
603
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type FileChange, type PackageManager, type PlannedFile } from './shared.ts';
|
|
2
|
+
export type ReactNativePlatform = 'ios' | 'android';
|
|
3
|
+
export interface InitReactNativeOptions {
|
|
4
|
+
root?: string;
|
|
5
|
+
defaultPlatform?: ReactNativePlatform;
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
force?: boolean;
|
|
8
|
+
metroFallback?: boolean;
|
|
9
|
+
packageManager?: PackageManager;
|
|
10
|
+
zntcVersion?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface InitReactNativeResult {
|
|
13
|
+
root: string;
|
|
14
|
+
changes: FileChange[];
|
|
15
|
+
dryRun: boolean;
|
|
16
|
+
installCommand: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const DEFAULT_RN_ENTRY = "index.js";
|
|
19
|
+
export declare const DEFAULT_RN_PLATFORM: ReactNativePlatform;
|
|
20
|
+
export declare function createReactNativeConfig(): string;
|
|
21
|
+
export declare function planReactNativeInit(options?: InitReactNativeOptions): PlannedFile[];
|
|
22
|
+
export declare function initReactNativeProject(options?: InitReactNativeOptions): InitReactNativeResult;
|
package/dist/rspack.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type FileChange, type PackageManager, type PlannedFile } from './shared.ts';
|
|
2
|
+
export type RspackBundler = 'rspack' | 'webpack';
|
|
3
|
+
export interface InitRspackOptions {
|
|
4
|
+
root?: string;
|
|
5
|
+
dryRun?: boolean;
|
|
6
|
+
force?: boolean;
|
|
7
|
+
packageManager?: PackageManager;
|
|
8
|
+
zntcVersion?: string;
|
|
9
|
+
/** 강제로 bundler 종류를 지정 (default: package.json 으로 자동 추론) */
|
|
10
|
+
bundler?: RspackBundler;
|
|
11
|
+
}
|
|
12
|
+
export interface InitRspackResult {
|
|
13
|
+
root: string;
|
|
14
|
+
changes: FileChange[];
|
|
15
|
+
dryRun: boolean;
|
|
16
|
+
installCommand: string;
|
|
17
|
+
bundler: RspackBundler;
|
|
18
|
+
}
|
|
19
|
+
export declare function createRspackConfig(bundler: RspackBundler): string;
|
|
20
|
+
export declare function planRspackInit(options?: InitRspackOptions): {
|
|
21
|
+
files: PlannedFile[];
|
|
22
|
+
bundler: RspackBundler;
|
|
23
|
+
};
|
|
24
|
+
export declare function initRspackProject(options?: InitRspackOptions): InitRspackResult;
|