create-textmode 1.0.7 → 1.1.1
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/README.md +5 -5
- package/bin/index.js +2 -2
- package/package.json +74 -68
- package/src/args.js +43 -40
- package/src/banner.js +42 -42
- package/src/cli.js +137 -142
- package/src/constants.js +33 -33
- package/src/fs-utils.js +100 -103
- package/src/packageManager.js +13 -13
- package/src/prompts.js +66 -65
- package/src/runCommand.js +38 -38
- package/src/summary.js +28 -33
- package/src/textmodeVersion.js +46 -46
- package/src/usage.js +19 -17
- package/src/versions.js +36 -36
- package/templates/vanilla-js/package.json +7 -7
- package/templates/vanilla-js-tweakpane/package.json +7 -7
- package/templates/vanilla-ts/package.json +10 -10
- package/templates/vanilla-ts/tsconfig.json +1 -1
- package/templates/vanilla-ts-tweakpane/package.json +10 -10
- package/templates/vanilla-ts-tweakpane/tsconfig.json +1 -1
package/src/cli.js
CHANGED
|
@@ -9,14 +9,14 @@ import { runCommand, runCommandLogged } from './runCommand.js';
|
|
|
9
9
|
import { printUsage } from './usage.js';
|
|
10
10
|
import { addons, templates } from './constants.js';
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
promptAddons,
|
|
13
|
+
promptInstall,
|
|
14
|
+
promptProjectName,
|
|
15
|
+
promptRun,
|
|
16
|
+
promptTemplate,
|
|
17
|
+
promptOverwrite,
|
|
18
|
+
suggestProjectName,
|
|
19
|
+
promptTextmodeVersion,
|
|
20
20
|
} from './prompts.js';
|
|
21
21
|
import { printHeader } from './banner.js';
|
|
22
22
|
import { parseArgv, ensureKnownAddons, ensureKnownTemplate, parseAddons } from './args.js';
|
|
@@ -27,140 +27,135 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
27
27
|
const __dirname = path.dirname(__filename);
|
|
28
28
|
|
|
29
29
|
export async function run() {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// --- Summary ---
|
|
161
|
-
log.message('');
|
|
162
|
-
printSummary({ projectName, pm, pmCmds, installDone, runDone, addons: selectedAddons });
|
|
163
|
-
outro(kleur.green('Enjoy textmode.js! ツ'));
|
|
30
|
+
await printHeader();
|
|
31
|
+
intro(
|
|
32
|
+
kleur.cyan('Welcome to textmode.js - let’s scaffold a new project! ✿ ') +
|
|
33
|
+
kleur.gray('(Press Ctrl+C at any time to cancel.)')
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const argv = parseArgv(process.argv.slice(2));
|
|
37
|
+
|
|
38
|
+
if (argv.help) {
|
|
39
|
+
printUsage();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (argv.version) {
|
|
44
|
+
const pkgJson = JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
45
|
+
console.log(pkgJson.version);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- Gather project info ---
|
|
50
|
+
let projectName = argv._[0] || argv.name;
|
|
51
|
+
if (!projectName) {
|
|
52
|
+
const suggested = suggestProjectName();
|
|
53
|
+
projectName = await promptProjectName(suggested);
|
|
54
|
+
}
|
|
55
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
56
|
+
|
|
57
|
+
ensureKnownTemplate(argv.template);
|
|
58
|
+
let templateName = argv.template;
|
|
59
|
+
if (!templateName) {
|
|
60
|
+
templateName = await promptTemplate();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const template = templates.find((t) => t.name === templateName);
|
|
64
|
+
const templateDir = path.join(__dirname, '..', 'templates', template.dir);
|
|
65
|
+
|
|
66
|
+
// --- Resolve add-ons ---
|
|
67
|
+
ensureKnownAddons(argv.addons);
|
|
68
|
+
let selectedAddons = parseAddons(argv.addons);
|
|
69
|
+
if (selectedAddons.length === 0 && !argv.addons) {
|
|
70
|
+
selectedAddons = (await promptAddons()).map((name) => addons.find((a) => a.name === name)).filter(Boolean);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// --- Resolve textmode.js version ---
|
|
74
|
+
// Only versions >= the global minimum are offered; add-ons are always at
|
|
75
|
+
// their latest version, so they impose no additional floor here.
|
|
76
|
+
const requestedTextmodeVersion = argv['textmode-version'];
|
|
77
|
+
const { textmodeVersion } = await resolveTextmodeVersion(requestedTextmodeVersion, promptTextmodeVersion);
|
|
78
|
+
|
|
79
|
+
// --- Pre-scaffold checks ---
|
|
80
|
+
const pm = argv.pm || detectPackageManager();
|
|
81
|
+
const pmCmds = pmCommands(pm);
|
|
82
|
+
|
|
83
|
+
const targetExists = await pathExists(targetDir);
|
|
84
|
+
if (targetExists && !(await isEmptyDir(targetDir)) && !argv.force) {
|
|
85
|
+
const overwrite = await promptOverwrite(targetDir);
|
|
86
|
+
if (!overwrite) {
|
|
87
|
+
outro('Aborted.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// --- Scaffold project ---
|
|
93
|
+
const scaffoldSpin = spinner();
|
|
94
|
+
scaffoldSpin.start('Scaffolding project...');
|
|
95
|
+
await scaffoldTemplate({ templateDir, targetDir, projectName, textmodeVersion, addons: selectedAddons });
|
|
96
|
+
scaffoldSpin.stop('Scaffold complete.');
|
|
97
|
+
|
|
98
|
+
// --- Install dependencies ---
|
|
99
|
+
let installDone = false;
|
|
100
|
+
|
|
101
|
+
const userInstall = argv.install;
|
|
102
|
+
const userNoInstall = argv['no-install'];
|
|
103
|
+
const hasInstallFlag = userInstall !== null || userNoInstall === true;
|
|
104
|
+
let doInstall = userInstall === true;
|
|
105
|
+
|
|
106
|
+
if (!hasInstallFlag) {
|
|
107
|
+
doInstall = await promptInstall(pm);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (doInstall) {
|
|
111
|
+
const installSpin = spinner();
|
|
112
|
+
installSpin.start(`Installing dependencies with ${pm}...`);
|
|
113
|
+
let seenOutput = false;
|
|
114
|
+
try {
|
|
115
|
+
await runCommandLogged(pm, pmCmds.install, targetDir, (line) => {
|
|
116
|
+
if (!seenOutput) {
|
|
117
|
+
installSpin.stop(`Installing dependencies with ${pm}...`);
|
|
118
|
+
seenOutput = true;
|
|
119
|
+
}
|
|
120
|
+
console.log(`│ ${line}`);
|
|
121
|
+
});
|
|
122
|
+
installDone = true;
|
|
123
|
+
if (!seenOutput) installSpin.stop('Dependencies installed.');
|
|
124
|
+
log.success('Dependencies installed.');
|
|
125
|
+
} catch (err) {
|
|
126
|
+
installSpin.stop('Dependency installation failed.');
|
|
127
|
+
log.error(`Dependency installation failed: ${err.message || err}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- Run dev server ---
|
|
132
|
+
let runDone = false;
|
|
133
|
+
|
|
134
|
+
if (installDone) {
|
|
135
|
+
const userRun = argv.run;
|
|
136
|
+
const userNoRun = argv['no-run'];
|
|
137
|
+
const hasRunFlag = userRun !== null || userNoRun === true;
|
|
138
|
+
let doRun = userRun === true;
|
|
139
|
+
|
|
140
|
+
if (!hasRunFlag) {
|
|
141
|
+
doRun = await promptRun(pm);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (doRun) {
|
|
145
|
+
log.step('Starting dev server...');
|
|
146
|
+
try {
|
|
147
|
+
await runCommand(pm, pmCmds.runDev, targetDir);
|
|
148
|
+
runDone = true;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
log.error(err.message || String(err));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// --- Summary ---
|
|
156
|
+
log.message('');
|
|
157
|
+
printSummary({ projectName, pm, pmCmds, installDone, runDone, addons: selectedAddons });
|
|
158
|
+
outro(kleur.green('Enjoy textmode.js! ツ'));
|
|
164
159
|
}
|
|
165
160
|
|
|
166
161
|
export default run;
|
package/src/constants.js
CHANGED
|
@@ -8,49 +8,49 @@ export const HEADER = `
|
|
|
8
8
|
`;
|
|
9
9
|
|
|
10
10
|
export const templates = [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
{ name: 'vanilla-js', label: 'Vanilla JavaScript (vite)', dir: 'vanilla-js' },
|
|
12
|
+
{ name: 'vanilla-ts', label: 'Vanilla TypeScript (vite)', dir: 'vanilla-ts' },
|
|
13
|
+
{ name: 'vanilla-js-tweakpane', label: 'Vanilla JavaScript + Tweakpane (vite)', dir: 'vanilla-js-tweakpane' },
|
|
14
|
+
{ name: 'vanilla-ts-tweakpane', label: 'Vanilla TypeScript + Tweakpane (vite)', dir: 'vanilla-ts-tweakpane' },
|
|
15
15
|
];
|
|
16
16
|
|
|
17
17
|
// Official textmode.js add-on libraries that can be pre-installed. The `plugin`
|
|
18
18
|
// value is the named ESM export each package provides. Add-ons are always
|
|
19
19
|
// installed at their latest version.
|
|
20
20
|
export const addons = [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
21
|
+
{
|
|
22
|
+
name: 'export',
|
|
23
|
+
label: 'textmode.export.js',
|
|
24
|
+
description: 'Export finished artworks (PNG, GIF, video, JSON, SVG, TXT)',
|
|
25
|
+
package: 'textmode.export.js',
|
|
26
|
+
plugin: 'ExportPlugin',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'synth',
|
|
30
|
+
label: 'textmode.synth.js',
|
|
31
|
+
description: 'Shader-backed live-coded synth scenes',
|
|
32
|
+
package: 'textmode.synth.js',
|
|
33
|
+
plugin: 'SynthPlugin',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'figlet',
|
|
37
|
+
label: 'textmode.figlet.js',
|
|
38
|
+
description: 'FIGlet display typography',
|
|
39
|
+
package: 'textmode.figlet.js',
|
|
40
|
+
plugin: 'FigletPlugin',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'filters',
|
|
44
|
+
label: 'textmode.filters.js',
|
|
45
|
+
description: 'GPU-accelerated image filters',
|
|
46
|
+
package: 'textmode.filters.js',
|
|
47
|
+
plugin: 'FiltersPlugin',
|
|
48
|
+
},
|
|
49
49
|
];
|
|
50
50
|
|
|
51
51
|
// Minimum textmode.js version the scaffolder will offer. All official add-ons
|
|
52
52
|
// peer-depend on a lower bound well below this floor, so it also covers them.
|
|
53
|
-
export const MIN_TEXTMODE_VERSION = '0.
|
|
53
|
+
export const MIN_TEXTMODE_VERSION = '0.18.0';
|
|
54
54
|
|
|
55
55
|
// File name patterns considered text for placeholder replacement.
|
|
56
56
|
export const TEXT_FILE_REGEX = /\.(json|js|jsx|ts|tsx|vue|md|html|txt|cjs|mjs)$/i;
|
package/src/fs-utils.js
CHANGED
|
@@ -3,134 +3,131 @@ import { cp, mkdir, readdir, readFile, rename, stat, writeFile } from 'fs/promis
|
|
|
3
3
|
import { IGNORED_DIR_ENTRIES, TEXT_FILE_REGEX } from './constants.js';
|
|
4
4
|
|
|
5
5
|
export async function pathExists(p) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
try {
|
|
7
|
+
await stat(p);
|
|
8
|
+
return true;
|
|
9
|
+
} catch (err) {
|
|
10
|
+
if (err.code === 'ENOENT') return false;
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export async function isEmptyDir(dir) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
try {
|
|
17
|
+
const entries = await readdir(dir);
|
|
18
|
+
const filtered = entries.filter((name) => !IGNORED_DIR_ENTRIES.includes(name));
|
|
19
|
+
return filtered.length === 0;
|
|
20
|
+
} catch (err) {
|
|
21
|
+
if (err.code === 'ENOENT') return true;
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async function replacePlaceholders(root, projectName) {
|
|
27
|
-
|
|
27
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
const fullPath = path.join(root, entry.name);
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
await replacePlaceholders(fullPath, projectName);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
if (entry.name === '_gitignore') {
|
|
38
|
+
await rename(fullPath, path.join(root, '.gitignore'));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
if (!TEXT_FILE_REGEX.test(entry.name)) continue;
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const data = await readFile(fullPath, 'utf8');
|
|
45
|
+
if (!data.includes('{{name}}')) continue;
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const next = data.replace(/{{name}}/g, projectName);
|
|
48
|
+
await writeFile(fullPath, next, 'utf8');
|
|
49
|
+
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export async function setPackageName(targetDir, projectName) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
54
|
+
try {
|
|
55
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
|
|
56
|
+
pkg.name = projectName;
|
|
57
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2));
|
|
58
|
+
} catch {
|
|
59
|
+
// If no package.json exists, ignore; templates without package.json are allowed.
|
|
60
|
+
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export async function setTextmodeVersion(targetDir, textmodeVersion) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
64
|
+
if (!textmodeVersion) return;
|
|
65
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
66
|
+
try {
|
|
67
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
|
|
68
|
+
if (!pkg.dependencies) pkg.dependencies = {};
|
|
69
|
+
if (pkg.dependencies['textmode.js'] !== undefined) {
|
|
70
|
+
pkg.dependencies['textmode.js'] = textmodeVersion;
|
|
71
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2));
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Ignore templates without package.json or invalid JSON.
|
|
75
|
+
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
export async function addAddonDependencies(targetDir, addons) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
if (!addons || addons.length === 0) return;
|
|
80
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
81
|
+
try {
|
|
82
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
|
|
83
|
+
if (!pkg.dependencies) pkg.dependencies = {};
|
|
84
|
+
for (const addon of addons) {
|
|
85
|
+
pkg.dependencies[addon.package] = 'latest';
|
|
86
|
+
}
|
|
87
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2));
|
|
88
|
+
} catch {
|
|
89
|
+
// Ignore templates without package.json or invalid JSON.
|
|
90
|
+
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export async function wireAddonsIntoSketch(targetDir, addons) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
await writeFile(filePath, data, 'utf8');
|
|
94
|
+
if (!addons || addons.length === 0) return;
|
|
95
|
+
|
|
96
|
+
const tsPath = path.join(targetDir, 'src', 'sketch.ts');
|
|
97
|
+
const filePath = (await pathExists(tsPath)) ? tsPath : path.join(targetDir, 'src', 'sketch.js');
|
|
98
|
+
if (!(await pathExists(filePath))) return;
|
|
99
|
+
|
|
100
|
+
let data = await readFile(filePath, 'utf8');
|
|
101
|
+
|
|
102
|
+
// Insert add-on imports directly after the textmode.js import.
|
|
103
|
+
const importLines = addons.map((a) => `import { ${a.plugin} } from '${a.package}';`).join('\n');
|
|
104
|
+
data = data.replace(/(import\s*\{[^}]*textmode[^}]*\}\s*from\s*'textmode\.js';)/, `$1\n${importLines}`);
|
|
105
|
+
|
|
106
|
+
// Register the plugins on the textmode.create({...}) call.
|
|
107
|
+
const pluginsStr = addons.map((a) => a.plugin).join(', ');
|
|
108
|
+
data = data.replace(/(textmode\.create\(\{)([^}]*)(\}\))/, (match, open, body, close) => {
|
|
109
|
+
if (/\bplugins\s*:/.test(body)) {
|
|
110
|
+
// Existing plugins key: append to the array.
|
|
111
|
+
const merged = body.replace(
|
|
112
|
+
/(plugins:\s*\[)([^\]]*)(\])/,
|
|
113
|
+
(m, p1, p2, p3) => p1 + (p2.trim() ? `${p2.trim()}, ` : '') + pluginsStr + p3
|
|
114
|
+
);
|
|
115
|
+
return open + ' ' + merged.trim() + ' ' + close;
|
|
116
|
+
}
|
|
117
|
+
const inner = body.trim();
|
|
118
|
+
return open + (inner ? ` ${inner}, ` : ' ') + `plugins: [${pluginsStr}] ` + close;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await writeFile(filePath, data, 'utf8');
|
|
125
122
|
}
|
|
126
123
|
|
|
127
124
|
export async function scaffoldTemplate({ templateDir, targetDir, projectName, textmodeVersion, addons = [] }) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
125
|
+
await mkdir(targetDir, { recursive: true });
|
|
126
|
+
await cp(templateDir, targetDir, { recursive: true, force: true });
|
|
127
|
+
|
|
128
|
+
await replacePlaceholders(targetDir, projectName);
|
|
129
|
+
await setPackageName(targetDir, projectName);
|
|
130
|
+
await setTextmodeVersion(targetDir, textmodeVersion);
|
|
131
|
+
await addAddonDependencies(targetDir, addons);
|
|
132
|
+
await wireAddonsIntoSketch(targetDir, addons);
|
|
136
133
|
}
|
package/src/packageManager.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export function detectPackageManager() {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
const ua = process.env.npm_config_user_agent || '';
|
|
3
|
+
if (ua.includes('pnpm')) return 'pnpm';
|
|
4
|
+
if (ua.includes('yarn')) return 'yarn';
|
|
5
|
+
if (ua.includes('bun')) return 'bun';
|
|
6
|
+
return 'npm';
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function pmCommands(pm) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
switch (pm) {
|
|
11
|
+
case 'pnpm':
|
|
12
|
+
case 'yarn':
|
|
13
|
+
case 'bun':
|
|
14
|
+
return { install: ['install'], runDev: ['run', 'dev'] };
|
|
15
|
+
default:
|
|
16
|
+
return { install: ['install'], runDev: ['run', 'dev'] };
|
|
17
|
+
}
|
|
18
18
|
}
|