create-nevela 0.1.3 → 0.1.5

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/template.mjs CHANGED
@@ -1,94 +1,127 @@
1
- // Where the new app's files come from.
2
- //
3
- // Published to npm, this package carries a `template/` folder built by build-template.mjs.
4
- // Run from the repository, it reads apps/web and packages/laravel directly, so there is
5
- // one source for the dashboard and nothing to keep in sync by hand.
6
-
7
- import fs from 'node:fs';
8
- import path from 'node:path';
9
- import { fileURLToPath } from 'node:url';
10
-
11
- const here = path.dirname(fileURLToPath(import.meta.url));
12
- const bundled = path.join(here, 'template');
13
- const fromRepo = !fs.existsSync(bundled);
14
-
15
- export const sources = {
16
- web: fromRepo ? path.resolve(here, '..', '..', 'apps', 'web') : path.join(bundled, 'web'),
17
- laravel: fromRepo ? path.resolve(here, '..', 'laravel') : path.join(bundled, 'laravel'),
18
- };
19
-
20
- /** Never part of a new app: build output, installs and local settings. */
21
- const NEVER = new Set(['node_modules', '.next', '.env.local', 'tsconfig.tsbuildinfo', 'next-env.d.ts', 'vendor', '.phpunit.result.cache', '.phpunit.cache', 'composer.lock']);
22
-
23
- function copyTree(from, to, skip = () => false, relative = '') {
24
- fs.mkdirSync(to, { recursive: true });
25
- for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
26
- const inside = relative ? `${relative}/${entry.name}` : entry.name;
27
- if (NEVER.has(entry.name) || skip(inside, entry)) continue;
28
- const source = path.join(from, entry.name);
29
- // npm leaves .gitignore files out of a published package, so the bundled template
30
- // carries them as _gitignore (see buildTemplate) and they get their name back here.
31
- const target = path.join(to, entry.name === '_gitignore' ? '.gitignore' : entry.name);
32
- if (entry.isDirectory()) copyTree(source, target, skip, inside);
33
- else if (entry.isFile()) fs.copyFileSync(source, target);
34
- }
35
- }
36
-
37
- /**
38
- * What belongs to the framework repository's own example resources, not to a new app:
39
- * their descriptors, the registry that lists them, and their dashboard pages.
40
- */
41
- export function exampleResourceFilter(webRoot) {
42
- const resources = path.join(webRoot, 'resources');
43
- const slugs = new Set();
44
- if (fs.existsSync(resources)) {
45
- for (const file of fs.readdirSync(resources)) {
46
- if (!file.endsWith('.resource.ts')) continue;
47
- const slug = /slug:\s*"([^"]+)"/.exec(fs.readFileSync(path.join(resources, file), 'utf8'));
48
- if (slug) slugs.add(slug[1]);
49
- }
50
- }
51
- return (inside) =>
52
- /^resources\/[^/]+\.resource\.ts$/.test(inside)
53
- || inside === 'resources/index.ts'
54
- || [...slugs].some((slug) => inside === `app/dashboard/${slug}`);
55
- }
56
-
57
- /** The dashboard, without the repository's example resources, named for the new app. */
58
- export function copyWebTemplate(to, { name, title }) {
59
- copyTree(sources.web, to, exampleResourceFilter(sources.web));
60
-
61
- const packageFile = path.join(to, 'package.json');
62
- const manifest = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
63
- manifest.name = `${name}-web`;
64
- fs.writeFileSync(packageFile, `${JSON.stringify(manifest, null, 2)}\n`);
65
-
66
- const siteFile = path.join(to, 'lib', 'site.ts');
67
- const site = fs.readFileSync(siteFile, 'utf8')
68
- .replace('name: "Nevela"', `name: ${JSON.stringify(title)}`)
69
- .replace('description: "Nevela keeps', `description: "${title.replace(/"/g, '')} keeps`);
70
- fs.writeFileSync(siteFile, site);
71
-
72
- // The generator fills this in; it has to exist for the first build.
73
- fs.mkdirSync(path.join(to, 'resources'), { recursive: true });
74
- }
75
-
76
- /** The Laravel package, for installing by path until it is on Packagist. */
77
- export function copyLaravelPackage(to) {
78
- copyTree(sources.laravel, to, (inside) => inside === 'tests' || inside === 'phpunit.xml');
79
- }
80
-
81
- /** Used by build-template.mjs: the same filtered copies, into this package's template/. */
82
- export function buildTemplate(to) {
83
- copyTree(sources.web, path.join(to, 'web'), exampleResourceFilter(sources.web));
84
- copyLaravelPackage(path.join(to, 'laravel'));
85
- hideGitignores(to);
86
- }
87
-
88
- function hideGitignores(dir) {
89
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
90
- const full = path.join(dir, entry.name);
91
- if (entry.isDirectory()) hideGitignores(full);
92
- else if (entry.name === '.gitignore') fs.renameSync(full, path.join(dir, '_gitignore'));
93
- }
94
- }
1
+ // Where the new app's files come from.
2
+ //
3
+ // Published to npm, this package carries a `template/` folder built by build-template.mjs.
4
+ // Run from the repository, it reads apps/web and packages/laravel directly, so there is
5
+ // one source for the dashboard and nothing to keep in sync by hand.
6
+
7
+ import crypto from 'node:crypto';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const here = path.dirname(fileURLToPath(import.meta.url));
13
+ const bundled = path.join(here, 'template');
14
+ // In a checkout the dashboard two folders up is the source, even when a template/ folder
15
+ // is lying around from an earlier `npm pack`: that copy may be stale.
16
+ const fromRepo = fs.existsSync(path.resolve(here, '..', '..', 'apps', 'web', 'package.json')) || !fs.existsSync(bundled);
17
+
18
+ /** True when this is a checkout of the repository rather than the package from npm. */
19
+ export const fromRepository = fromRepo;
20
+
21
+ export const sources = {
22
+ web: fromRepo ? path.resolve(here, '..', '..', 'apps', 'web') : path.join(bundled, 'web'),
23
+ laravel: fromRepo ? path.resolve(here, '..', 'laravel') : path.join(bundled, 'laravel'),
24
+ };
25
+
26
+ /** Never part of a new app: build output, installs and local settings. */
27
+ const NEVER = new Set(['node_modules', '.next', '.env.local', 'tsconfig.tsbuildinfo', 'next-env.d.ts', 'vendor', '.phpunit.result.cache', '.phpunit.cache', 'composer.lock']);
28
+
29
+ function copyTree(from, to, skip = () => false, relative = '') {
30
+ fs.mkdirSync(to, { recursive: true });
31
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
32
+ const inside = relative ? `${relative}/${entry.name}` : entry.name;
33
+ if (NEVER.has(entry.name) || skip(inside, entry)) continue;
34
+ const source = path.join(from, entry.name);
35
+ // npm leaves .gitignore files out of a published package, so the bundled template
36
+ // carries them as _gitignore (see buildTemplate) and they get their name back here.
37
+ const target = path.join(to, entry.name === '_gitignore' ? '.gitignore' : entry.name);
38
+ if (entry.isDirectory()) copyTree(source, target, skip, inside);
39
+ else if (entry.isFile()) fs.copyFileSync(source, target);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * What belongs to the framework repository's own example resources, not to a new app:
45
+ * their descriptors, the registry that lists them, and their dashboard pages.
46
+ */
47
+ export function exampleResourceFilter(webRoot) {
48
+ const resources = path.join(webRoot, 'resources');
49
+ const slugs = new Set();
50
+ if (fs.existsSync(resources)) {
51
+ for (const file of fs.readdirSync(resources)) {
52
+ if (!file.endsWith('.resource.ts')) continue;
53
+ const slug = /slug:\s*"([^"]+)"/.exec(fs.readFileSync(path.join(resources, file), 'utf8'));
54
+ if (slug) slugs.add(slug[1]);
55
+ }
56
+ }
57
+ return (inside) =>
58
+ /^resources\/[^/]+\.resource\.ts$/.test(inside)
59
+ || inside === 'resources/index.ts'
60
+ || [...slugs].some((slug) => inside === `app/dashboard/${slug}`);
61
+ }
62
+
63
+ /** The dashboard, without the repository's example resources, named for the new app. */
64
+ export function copyWebTemplate(to, { name, title }) {
65
+ copyTree(sources.web, to, exampleResourceFilter(sources.web));
66
+
67
+ const packageFile = path.join(to, 'package.json');
68
+ const manifest = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
69
+ manifest.name = `${name}-web`;
70
+ fs.writeFileSync(packageFile, `${JSON.stringify(manifest, null, 2)}\n`);
71
+
72
+ const siteFile = path.join(to, 'lib', 'site.ts');
73
+ const site = fs.readFileSync(siteFile, 'utf8')
74
+ .replace('name: "Nevela"', `name: ${JSON.stringify(title)}`)
75
+ .replace('description: "Nevela keeps', `description: "${title.replace(/"/g, '')} keeps`);
76
+ fs.writeFileSync(siteFile, site);
77
+
78
+ // The generator fills this in; it has to exist for the first build.
79
+ fs.mkdirSync(path.join(to, 'resources'), { recursive: true });
80
+ }
81
+
82
+ /**
83
+ * A fingerprint of every template file, and the template's dependencies: what the app
84
+ * records in .nevela.json so that `nevela update` can tell, later and exactly, which
85
+ * files the developer changed. The same fingerprint the Laravel package computes: SHA-1
86
+ * with line endings ignored.
87
+ */
88
+ export function templateRecord(version) {
89
+ const skip = exampleResourceFilter(sources.web);
90
+ const files = {};
91
+ const walk = (dir, relative = '') => {
92
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
93
+ const inside = relative ? `${relative}/${entry.name}` : entry.name;
94
+ if (NEVER.has(entry.name) || skip(inside, entry)) continue;
95
+ if (entry.isDirectory()) walk(path.join(dir, entry.name), inside);
96
+ else if (entry.isFile()) {
97
+ const bytes = fs.readFileSync(path.join(dir, entry.name)).toString('latin1').replace(/\r\n/g, '\n');
98
+ files[inside.replace(/(^|\/)_gitignore$/, '$1.gitignore')] = crypto.createHash('sha1').update(bytes, 'latin1').digest('hex');
99
+ }
100
+ }
101
+ };
102
+ walk(sources.web);
103
+ const manifest = JSON.parse(fs.readFileSync(path.join(sources.web, 'package.json'), 'utf8'));
104
+ const dependencies = {};
105
+ for (const section of ['dependencies', 'devDependencies']) if (manifest[section]) dependencies[section] = manifest[section];
106
+ return { template: version, files: Object.fromEntries(Object.entries(files).sort(([a], [b]) => a.localeCompare(b))), dependencies };
107
+ }
108
+
109
+ /** The Laravel package, for installing by path until it is on Packagist. */
110
+ export function copyLaravelPackage(to) {
111
+ copyTree(sources.laravel, to, (inside) => inside === 'tests' || inside === 'phpunit.xml');
112
+ }
113
+
114
+ /** Used by build-template.mjs: the same filtered copies, into this package's template/. */
115
+ export function buildTemplate(to) {
116
+ copyTree(sources.web, path.join(to, 'web'), exampleResourceFilter(sources.web));
117
+ copyLaravelPackage(path.join(to, 'laravel'));
118
+ hideGitignores(to);
119
+ }
120
+
121
+ function hideGitignores(dir) {
122
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
123
+ const full = path.join(dir, entry.name);
124
+ if (entry.isDirectory()) hideGitignores(full);
125
+ else if (entry.name === '.gitignore') fs.renameSync(full, path.join(dir, '_gitignore'));
126
+ }
127
+ }