-
-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathbuild-templates.js
200 lines (163 loc) · 6.03 KB
/
build-templates.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import fs from 'fs';
import path from 'path';
import parser from 'gitignore-parser';
import prettier from 'prettier';
import { transform } from 'sucrase';
import glob from 'tiny-glob/sync.js';
import { mkdirp, rimraf } from '../utils.js';
/** @param {Set<string>} shared */
async function generate_templates(shared) {
const templates = fs.readdirSync('templates');
for (const template of templates) {
const dir = `dist/templates/${template}`;
const assets = `${dir}/assets`;
mkdirp(assets);
const cwd = path.resolve('templates', template);
const gitignore_file = path.join(cwd, '.gitignore');
if (!fs.existsSync(gitignore_file)) throw new Error('Template must have a .gitignore file');
const gitignore = parser.compile(fs.readFileSync(gitignore_file, 'utf-8'));
const ignore_file = path.join(cwd, '.ignore');
if (!fs.existsSync(ignore_file)) throw new Error('Template must have a .ignore file');
const ignore = parser.compile(fs.readFileSync(ignore_file, 'utf-8'));
const meta_file = path.join(cwd, '.meta.json');
if (!fs.existsSync(meta_file)) throw new Error('Template must have a .meta.json file');
/** @type {import('../types/internal.js').File[]} */
const ts = [];
glob('**/*', { cwd, filesOnly: true, dot: true }).forEach((name) => {
// the package.template.json thing is a bit annoying — basically we want
// to be able to develop and deploy the app from here, but have a different
// package.json in newly created projects (based on package.template.json)
if (name === 'package.template.json') {
let contents = fs.readFileSync(path.join(cwd, name), 'utf8');
// TODO package-specific versions
contents = contents.replace(/workspace:\*/g, 'next');
fs.writeFileSync(`${dir}/package.json`, contents);
return;
}
// ignore files that are written conditionally
if (shared.has(name)) return;
// ignore contents of .gitignore or .ignore
if (!gitignore.accepts(name) || !ignore.accepts(name) || name === '.ignore') return;
if (/\.(js|ts|svelte|svelte\.md)$/.test(name)) {
const contents = fs.readFileSync(path.join(cwd, name), 'utf8');
ts.push({
name,
contents
});
} else {
const dest = path.join(assets, name.replace(/^\./, 'DOT-'));
mkdirp(path.dirname(dest));
fs.copyFileSync(path.join(cwd, name), dest);
}
});
/** @type {import('../types/internal.js').File[]} */
const js = [];
for (const file of ts) {
// The app.d.ts file makes TS/JS aware of some ambient modules, which are
// also needed for JS projects if people turn on "checkJs" in their jsonfig
if (file.name.endsWith('.d.ts')) {
if (file.name.endsWith('app.d.ts')) js.push(file);
} else if (file.name.endsWith('.ts')) {
const transformed = transform(file.contents, {
transforms: ['typescript']
});
const contents = prettier.format(transformed.code, {
parser: 'babel',
useTabs: true,
singleQuote: true,
trailingComma: 'none',
printWidth: 100
});
js.push({
name: file.name.replace(/\.ts$/, '.js'),
contents
});
} else if (file.name.endsWith('.svelte')) {
// we jump through some hoops, rather than just using svelte.preprocess,
// so that the output preserves the original formatting to the extent
// possible (e.g. preserving double line breaks). Sucrase is the best
// tool for the job because it just removes the types; Prettier then
// tidies up the end result
const contents = file.contents.replace(
/<script([^>]+)>([\s\S]+?)<\/script>/g,
(m, attrs, typescript) => {
// Sucrase assumes 'unused' imports (which _are_ used, but only
// in the markup) are type imports, and strips them. This step
// prevents it from drawing that conclusion
const imports = [];
const import_pattern = /import (.+?) from/g;
let import_match;
while ((import_match = import_pattern.exec(typescript))) {
const word_pattern = /[a-z_$][a-z0-9_$]*/gi;
let word_match;
while ((word_match = word_pattern.exec(import_match[1]))) {
imports.push(word_match[0]);
}
}
const suffix = `\n${imports.join(',')}`;
const transformed = transform(typescript + suffix, {
transforms: ['typescript']
}).code.slice(0, -suffix.length);
const contents = prettier
.format(transformed, {
parser: 'babel',
useTabs: true,
singleQuote: true,
trailingComma: 'none',
printWidth: 100
})
.trim()
.replace(/^(.)/gm, '\t$1');
return `<script${attrs.replace(' lang="ts"', '')}>\n${contents}\n</script>`;
}
);
js.push({
name: file.name,
contents
});
} else {
js.push(file);
}
}
fs.copyFileSync(meta_file, `${dir}/meta.json`);
fs.writeFileSync(`${dir}/files.ts.json`, JSON.stringify(ts, null, '\t'));
fs.writeFileSync(`${dir}/files.js.json`, JSON.stringify(js, null, '\t'));
}
}
async function generate_shared() {
const cwd = path.resolve('shared');
/** @type {Set<string>} */
const shared = new Set();
const files = glob('**/*', { cwd, filesOnly: true, dot: true })
.map((file) => {
const contents = fs.readFileSync(path.join(cwd, file), 'utf8');
/** @type {string[]} */
const include = [];
/** @type {string[]} */
const exclude = [];
let name = file;
if (file.startsWith('+') || file.startsWith('-')) {
const [conditions, ...rest] = file.split(path.sep);
const pattern = /([+-])([a-z]+)/g;
let match;
while ((match = pattern.exec(conditions))) {
const set = match[1] === '+' ? include : exclude;
set.push(match[2]);
}
name = rest.join('/');
}
shared.add(name);
return { name, include, exclude, contents };
})
.sort((a, b) => a.include.length + a.exclude.length - (b.include.length + b.exclude.length));
fs.writeFileSync('dist/shared.json', JSON.stringify({ files }, null, '\t'));
shared.delete('package.json');
return shared;
}
async function main() {
rimraf('dist');
mkdirp('dist');
const shared = await generate_shared();
await generate_templates(shared);
}
main();