@nabilabs/create-builder 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 +56 -0
- package/dist/cli.js +363 -0
- package/dist/index.js +293 -0
- package/package.json +80 -0
- package/templates/default/.editorconfig +12 -0
- package/templates/default/.prettierrc +6 -0
- package/templates/default/README.md +22 -0
- package/templates/default/eslint.config.js +15 -0
- package/templates/default/gitignore +3 -0
- package/templates/default/nabi.config.js +7 -0
- package/templates/default/package.json +24 -0
- package/templates/default/src/data/.gitkeep +0 -0
- package/templates/default/src/pages/about/index.html +78 -0
- package/templates/default/src/pages/about/style.css +209 -0
- package/templates/default/src/pages/index.html +95 -0
- package/templates/default/src/pages/style.css +257 -0
- package/templates/default/src/shared/assets/mark.svg +9 -0
- package/templates/default/src/shared/assets/shortcut.svg +9 -0
- package/templates/default/src/shared/js/.gitkeep +1 -0
- package/templates/default/src/shared/styles/base.css +92 -0
- package/templates/default/src/shared/styles/normalize.css +55 -0
- package/templates/default/src/ui/footer/index.html +6 -0
- package/templates/default/src/ui/footer/style.css +26 -0
- package/templates/default/src/ui/head/index.html +29 -0
- package/templates/default/src/ui/header/index.html +17 -0
- package/templates/default/src/ui/header/style.css +67 -0
- package/templates/default/stylelint.config.mjs +14 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { access, cp, lstat, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
class CreateBuilderError extends Error {
|
|
9
|
+
constructor(message, options) {
|
|
10
|
+
super(message, options);
|
|
11
|
+
this.name = "CreateBuilderError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/index.ts
|
|
16
|
+
var templatesDirectory = resolve(dirname(fileURLToPath(import.meta.url)), "../templates");
|
|
17
|
+
var packageManagerVariable = /\{\{packageManager\}\}/g;
|
|
18
|
+
var templateVariable = /\{\{projectName\}\}/g;
|
|
19
|
+
var packageManagerFromUserAgent = (userAgent) => {
|
|
20
|
+
const invokingUserAgent = userAgent ?? process.env.npm_config_user_agent;
|
|
21
|
+
if (invokingUserAgent?.startsWith("bun/"))
|
|
22
|
+
return "bun";
|
|
23
|
+
if (invokingUserAgent?.startsWith("pnpm/"))
|
|
24
|
+
return "pnpm";
|
|
25
|
+
if (invokingUserAgent?.startsWith("yarn/"))
|
|
26
|
+
return "yarn";
|
|
27
|
+
if (userAgent === undefined && process.versions.bun)
|
|
28
|
+
return "bun";
|
|
29
|
+
return "npm";
|
|
30
|
+
};
|
|
31
|
+
var packageManagerSpecificationFromUserAgent = (packageManager, userAgent) => {
|
|
32
|
+
const invokingUserAgent = userAgent ?? process.env.npm_config_user_agent;
|
|
33
|
+
const version = invokingUserAgent?.match(new RegExp(`(?:^|\\s)${packageManager}/([^\\s]+)`))?.[1];
|
|
34
|
+
const runtimeVersion = packageManager === "bun" ? process.versions.bun : undefined;
|
|
35
|
+
const resolvedVersion = version ?? runtimeVersion;
|
|
36
|
+
return resolvedVersion ? `${packageManager}@${resolvedVersion}` : packageManager;
|
|
37
|
+
};
|
|
38
|
+
var isMissing = (error) => error instanceof Error && ("code" in error) && error.code === "ENOENT";
|
|
39
|
+
var validateProjectName = (projectName) => {
|
|
40
|
+
const name = projectName.trim();
|
|
41
|
+
if (!name)
|
|
42
|
+
throw new CreateBuilderError("Project name cannot be empty.");
|
|
43
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(name)) {
|
|
44
|
+
throw new CreateBuilderError("Project name may only contain letters, numbers, dots, underscores, and hyphens.");
|
|
45
|
+
}
|
|
46
|
+
if (name === "." || name === "..")
|
|
47
|
+
throw new CreateBuilderError("Project name must name a new directory.");
|
|
48
|
+
return name;
|
|
49
|
+
};
|
|
50
|
+
var getTemplatePath = async (template) => {
|
|
51
|
+
if (!/^[a-z0-9][a-z0-9-]*$/i.test(template))
|
|
52
|
+
throw new CreateBuilderError(`Invalid template: ${template}`);
|
|
53
|
+
const path = resolve(templatesDirectory, template);
|
|
54
|
+
if (dirname(path) !== templatesDirectory)
|
|
55
|
+
throw new CreateBuilderError(`Invalid template: ${template}`);
|
|
56
|
+
try {
|
|
57
|
+
const stats = await lstat(path);
|
|
58
|
+
if (!stats.isDirectory())
|
|
59
|
+
throw new CreateBuilderError(`Template is not a directory: ${template}`);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (isMissing(error))
|
|
62
|
+
throw new CreateBuilderError(`Unknown template: ${template}`);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
return path;
|
|
66
|
+
};
|
|
67
|
+
var getTemplateNames = async () => {
|
|
68
|
+
const entries = await readdir(templatesDirectory, { withFileTypes: true });
|
|
69
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((first, second) => first.localeCompare(second));
|
|
70
|
+
};
|
|
71
|
+
var validateTargetDirectory = async ({
|
|
72
|
+
cwd,
|
|
73
|
+
projectName
|
|
74
|
+
}) => {
|
|
75
|
+
const directory = resolve(cwd, projectName);
|
|
76
|
+
if (basename(directory) !== projectName)
|
|
77
|
+
throw new CreateBuilderError("Project name must not contain a path.");
|
|
78
|
+
try {
|
|
79
|
+
await access(directory);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (isMissing(error))
|
|
82
|
+
return directory;
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
throw new CreateBuilderError(`Directory already exists: ${directory}`);
|
|
86
|
+
};
|
|
87
|
+
var validateCurrentDirectory = async (directory) => {
|
|
88
|
+
const stats = await lstat(directory);
|
|
89
|
+
if (!stats.isDirectory())
|
|
90
|
+
throw new CreateBuilderError(`Current path is not a directory: ${directory}`);
|
|
91
|
+
const entries = await readdir(directory);
|
|
92
|
+
let hasPackageManifest = false;
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
const path = resolve(directory, entry);
|
|
95
|
+
const entryStats = await lstat(path);
|
|
96
|
+
if (entry === "package.json" && entryStats.isFile()) {
|
|
97
|
+
hasPackageManifest = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (entry === "node_modules" && entryStats.isDirectory())
|
|
101
|
+
continue;
|
|
102
|
+
throw new CreateBuilderError(`Current directory may only contain package.json and node_modules: ${directory}`);
|
|
103
|
+
}
|
|
104
|
+
return hasPackageManifest;
|
|
105
|
+
};
|
|
106
|
+
var resolveProjectTarget = async ({
|
|
107
|
+
cwd,
|
|
108
|
+
projectName
|
|
109
|
+
}) => {
|
|
110
|
+
if (projectName === "." || projectName === "./") {
|
|
111
|
+
const directory = resolve(cwd);
|
|
112
|
+
const hasPackageManifest = await validateCurrentDirectory(directory);
|
|
113
|
+
return {
|
|
114
|
+
directory,
|
|
115
|
+
hasPackageManifest,
|
|
116
|
+
isCurrentDirectory: true,
|
|
117
|
+
projectName: validateProjectName(basename(directory))
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const name = validateProjectName(projectName);
|
|
121
|
+
return {
|
|
122
|
+
directory: await validateTargetDirectory({ cwd, projectName: name }),
|
|
123
|
+
hasPackageManifest: false,
|
|
124
|
+
isCurrentDirectory: false,
|
|
125
|
+
projectName: name
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
var copyTemplate = async ({
|
|
129
|
+
destination,
|
|
130
|
+
destinationExists = false,
|
|
131
|
+
skipPackageManifest = false,
|
|
132
|
+
source
|
|
133
|
+
}) => {
|
|
134
|
+
try {
|
|
135
|
+
if (destinationExists) {
|
|
136
|
+
const entries = (await readdir(source)).filter((entry) => !skipPackageManifest || entry !== "package.json");
|
|
137
|
+
await Promise.all(entries.map((entry) => cp(resolve(source, entry), resolve(destination, entry), {
|
|
138
|
+
errorOnExist: true,
|
|
139
|
+
force: false,
|
|
140
|
+
recursive: true
|
|
141
|
+
})));
|
|
142
|
+
} else {
|
|
143
|
+
await cp(source, destination, {
|
|
144
|
+
errorOnExist: true,
|
|
145
|
+
force: false,
|
|
146
|
+
recursive: true
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
await rename(resolve(destination, "gitignore"), resolve(destination, ".gitignore"));
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
152
|
+
throw new CreateBuilderError(`Directory already exists: ${destination}`);
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
var isManifest = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
158
|
+
var readPackageManifest = async (path) => {
|
|
159
|
+
try {
|
|
160
|
+
const manifest = JSON.parse(await readFile(path, "utf8"));
|
|
161
|
+
if (!isManifest(manifest))
|
|
162
|
+
throw new CreateBuilderError(`Invalid package.json: ${path}`);
|
|
163
|
+
return manifest;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error instanceof SyntaxError)
|
|
166
|
+
throw new CreateBuilderError(`Invalid package.json: ${path}`);
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
var manifestSection = (manifest, section) => {
|
|
171
|
+
const value = manifest[section];
|
|
172
|
+
if (value === undefined)
|
|
173
|
+
return {};
|
|
174
|
+
if (!isManifest(value))
|
|
175
|
+
throw new CreateBuilderError(`Invalid package.json ${section}.`);
|
|
176
|
+
return value;
|
|
177
|
+
};
|
|
178
|
+
var mergePackageManifest = async ({
|
|
179
|
+
directory,
|
|
180
|
+
packageManagerSpecification,
|
|
181
|
+
projectName,
|
|
182
|
+
templatePath
|
|
183
|
+
}) => {
|
|
184
|
+
const path = resolve(directory, "package.json");
|
|
185
|
+
const [existing, templateManifest] = await Promise.all([
|
|
186
|
+
readPackageManifest(path),
|
|
187
|
+
readPackageManifest(resolve(templatePath, "package.json"))
|
|
188
|
+
]);
|
|
189
|
+
const manifest = {
|
|
190
|
+
...templateManifest,
|
|
191
|
+
...existing,
|
|
192
|
+
devDependencies: {
|
|
193
|
+
...manifestSection(existing, "devDependencies"),
|
|
194
|
+
...manifestSection(templateManifest, "devDependencies")
|
|
195
|
+
},
|
|
196
|
+
name: projectName,
|
|
197
|
+
packageManager: packageManagerSpecification,
|
|
198
|
+
scripts: {
|
|
199
|
+
...manifestSection(existing, "scripts"),
|
|
200
|
+
...manifestSection(templateManifest, "scripts")
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
await writeFile(path, `${JSON.stringify(manifest, null, 2)}
|
|
204
|
+
`, "utf8");
|
|
205
|
+
};
|
|
206
|
+
var getFiles = async (directory) => {
|
|
207
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
208
|
+
const paths = await Promise.all(entries.filter((entry) => entry.name !== "node_modules").map(async (entry) => {
|
|
209
|
+
const path = resolve(directory, entry.name);
|
|
210
|
+
if (entry.isDirectory())
|
|
211
|
+
return getFiles(path);
|
|
212
|
+
return [path];
|
|
213
|
+
}));
|
|
214
|
+
return paths.flat();
|
|
215
|
+
};
|
|
216
|
+
var replaceTemplateVariables = async ({
|
|
217
|
+
directory,
|
|
218
|
+
packageManagerSpecification,
|
|
219
|
+
projectName
|
|
220
|
+
}) => {
|
|
221
|
+
const files = await getFiles(directory);
|
|
222
|
+
await Promise.all(files.map(async (path) => {
|
|
223
|
+
const source = await readFile(path, "utf8");
|
|
224
|
+
const result = source.replace(templateVariable, projectName).replace(packageManagerVariable, packageManagerSpecification);
|
|
225
|
+
if (result !== source)
|
|
226
|
+
await writeFile(path, result, "utf8");
|
|
227
|
+
}));
|
|
228
|
+
};
|
|
229
|
+
var installDependencies = async (directory, packageManager) => {
|
|
230
|
+
await new Promise((resolve2, reject) => {
|
|
231
|
+
const child = spawn(packageManager, ["install"], {
|
|
232
|
+
cwd: directory,
|
|
233
|
+
shell: process.platform === "win32",
|
|
234
|
+
stdio: "inherit"
|
|
235
|
+
});
|
|
236
|
+
child.once("error", reject);
|
|
237
|
+
child.once("exit", (code) => {
|
|
238
|
+
if (code === 0)
|
|
239
|
+
return resolve2();
|
|
240
|
+
reject(new CreateBuilderError("Dependency installation failed."));
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
var createProject = async ({
|
|
245
|
+
cwd = process.cwd(),
|
|
246
|
+
install = installDependencies,
|
|
247
|
+
packageManager = "npm",
|
|
248
|
+
packageManagerSpecification = packageManager,
|
|
249
|
+
projectName,
|
|
250
|
+
template = "default"
|
|
251
|
+
}) => {
|
|
252
|
+
const target = await resolveProjectTarget({ cwd, projectName });
|
|
253
|
+
const source = await getTemplatePath(template);
|
|
254
|
+
if (target.hasPackageManifest)
|
|
255
|
+
await readPackageManifest(resolve(target.directory, "package.json"));
|
|
256
|
+
await copyTemplate({
|
|
257
|
+
destination: target.directory,
|
|
258
|
+
destinationExists: target.isCurrentDirectory,
|
|
259
|
+
skipPackageManifest: target.hasPackageManifest,
|
|
260
|
+
source
|
|
261
|
+
});
|
|
262
|
+
await replaceTemplateVariables({
|
|
263
|
+
directory: target.directory,
|
|
264
|
+
packageManagerSpecification,
|
|
265
|
+
projectName: target.projectName
|
|
266
|
+
});
|
|
267
|
+
if (target.hasPackageManifest) {
|
|
268
|
+
await mergePackageManifest({
|
|
269
|
+
directory: target.directory,
|
|
270
|
+
packageManagerSpecification,
|
|
271
|
+
projectName: target.projectName,
|
|
272
|
+
templatePath: source
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
await install(target.directory, packageManager);
|
|
276
|
+
return {
|
|
277
|
+
directory: target.directory,
|
|
278
|
+
projectName: target.projectName,
|
|
279
|
+
template
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
export {
|
|
283
|
+
validateTargetDirectory,
|
|
284
|
+
validateProjectName,
|
|
285
|
+
replaceTemplateVariables,
|
|
286
|
+
packageManagerSpecificationFromUserAgent,
|
|
287
|
+
packageManagerFromUserAgent,
|
|
288
|
+
installDependencies,
|
|
289
|
+
getTemplatePath,
|
|
290
|
+
getTemplateNames,
|
|
291
|
+
createProject,
|
|
292
|
+
copyTemplate
|
|
293
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nabilabs/create-builder",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publisher": "Nabi Labs",
|
|
5
|
+
"displayName": "Nabi Builder - Create",
|
|
6
|
+
"description": "Create a new Nabi Builder project",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"packageManager": "bun",
|
|
10
|
+
"bin": {
|
|
11
|
+
"create-builder": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "bun run scripts/build.ts",
|
|
15
|
+
"----------": "----------",
|
|
16
|
+
"lint": "eslint . --fix",
|
|
17
|
+
"lint:check": "eslint . --max-warnings 0",
|
|
18
|
+
"format": "prettier --write .",
|
|
19
|
+
"format:check": "prettier --check .",
|
|
20
|
+
"typecheck": "bunx tsc --noEmit",
|
|
21
|
+
"test": "bun test",
|
|
22
|
+
"pack:check": "bun pm pack --dry-run --ignore-scripts",
|
|
23
|
+
"-----------": "-----------",
|
|
24
|
+
"fix": "bun run lint && bun run format",
|
|
25
|
+
"check": "bun run typecheck && bun run lint:check && bun run format:check && bun run test && bun run build && bun run pack:check",
|
|
26
|
+
"------------": "------------",
|
|
27
|
+
"prepack": "bun run build",
|
|
28
|
+
"prepublishOnly": "bun run check",
|
|
29
|
+
"prepare": "bun run scripts/prepare.ts"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@eslint/js": "^9.39.0",
|
|
36
|
+
"@types/bun": "^1.4.0",
|
|
37
|
+
"@types/node": "^22.20.1",
|
|
38
|
+
"dts-bundle-generator": "^9.5.1",
|
|
39
|
+
"eslint": "^9.39.0",
|
|
40
|
+
"eslint-config-prettier": "^10.1.8",
|
|
41
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
42
|
+
"eslint-plugin-import": "^2.32.0",
|
|
43
|
+
"eslint-plugin-perfectionist": "^5.10.1",
|
|
44
|
+
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
45
|
+
"globals": "^17.4.0",
|
|
46
|
+
"husky": "^9.1.7",
|
|
47
|
+
"jiti": "^2.7.0",
|
|
48
|
+
"prettier": "^3.8.1",
|
|
49
|
+
"typescript": "5.9.3",
|
|
50
|
+
"typescript-eslint": "^8.68.0"
|
|
51
|
+
},
|
|
52
|
+
"keywords": [
|
|
53
|
+
"nabi",
|
|
54
|
+
"builder",
|
|
55
|
+
"html",
|
|
56
|
+
"ssg",
|
|
57
|
+
"static-site-generator",
|
|
58
|
+
"components",
|
|
59
|
+
"build-tool",
|
|
60
|
+
"create"
|
|
61
|
+
],
|
|
62
|
+
"files": [
|
|
63
|
+
"dist",
|
|
64
|
+
"templates",
|
|
65
|
+
"README.md",
|
|
66
|
+
"LICENSE"
|
|
67
|
+
],
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public",
|
|
70
|
+
"registry": "https://registry.npmjs.org/"
|
|
71
|
+
},
|
|
72
|
+
"repository": {
|
|
73
|
+
"type": "git",
|
|
74
|
+
"url": "git+https://github.com/nabilabshq/create-builder.git"
|
|
75
|
+
},
|
|
76
|
+
"bugs": {
|
|
77
|
+
"url": "https://github.com/nabilabshq/create-builder/issues"
|
|
78
|
+
},
|
|
79
|
+
"homepage": "https://github.com/nabilabshq/create-builder#readme"
|
|
80
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
Two-page static website built with [Nabi Builder](https://github.com/nabilabshq/builder).
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
npm run build
|
|
10
|
+
npm run check
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
With pnpm, use `pnpm run <script>`; with Yarn, use `yarn <script>`.
|
|
14
|
+
|
|
15
|
+
Start in `src/pages/index.html`. The template also includes the global `ui/head` component and `shared/styles/normalize.css`.
|
|
16
|
+
|
|
17
|
+
## Routes
|
|
18
|
+
|
|
19
|
+
- `/` — `src/pages/index.html`
|
|
20
|
+
- `/about` — `src/pages/about/index.html`
|
|
21
|
+
|
|
22
|
+
The template demonstrates shared UI components, colocated CSS, and assets referenced through `@assets`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
{ ignores: ["dist", "node_modules"] },
|
|
6
|
+
js.configs.recommended,
|
|
7
|
+
{
|
|
8
|
+
files: ["src/**/*.js"],
|
|
9
|
+
languageOptions: {
|
|
10
|
+
ecmaVersion: "latest",
|
|
11
|
+
globals: globals.browser,
|
|
12
|
+
sourceType: "module",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"packageManager": "{{packageManager}}",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "nabi dev",
|
|
9
|
+
"build": "nabi build",
|
|
10
|
+
"build:inline": "nabi build --mode inline",
|
|
11
|
+
"lint": "eslint . && stylelint \"src/**/*.css\"",
|
|
12
|
+
"format": "prettier . --write",
|
|
13
|
+
"check": "eslint . --max-warnings 0 && stylelint \"src/**/*.css\" && prettier . --check"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@eslint/js": "^9.39.0",
|
|
17
|
+
"@nabilabs/builder": "^0.2.0",
|
|
18
|
+
"eslint": "^9.39.0",
|
|
19
|
+
"globals": "^17.4.0",
|
|
20
|
+
"prettier": "^3.8.1",
|
|
21
|
+
"stylelint": "^16.24.0",
|
|
22
|
+
"stylelint-config-standard": "^39.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<use
|
|
4
|
+
ref="ui/head"
|
|
5
|
+
title="About — {{projectName}}"
|
|
6
|
+
description="The second page of this Nabi Builder starter project."
|
|
7
|
+
>
|
|
8
|
+
<link use="base.css" />
|
|
9
|
+
</use>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<use ref="ui/header" active="about" />
|
|
13
|
+
|
|
14
|
+
<main class="shell about">
|
|
15
|
+
<section class="about__hero">
|
|
16
|
+
<p class="eyebrow">About this sample</p>
|
|
17
|
+
<h1>
|
|
18
|
+
Multi-page
|
|
19
|
+
<span>without the machinery.</span>
|
|
20
|
+
</h1>
|
|
21
|
+
|
|
22
|
+
<p class="about__lead">
|
|
23
|
+
This page exists because there is an
|
|
24
|
+
<code>about/index.html</code> file. The shared header and footer are
|
|
25
|
+
resolved during the build, not in the browser.
|
|
26
|
+
</p>
|
|
27
|
+
</section>
|
|
28
|
+
|
|
29
|
+
<section class="principles">
|
|
30
|
+
<article class="principle principle--wide">
|
|
31
|
+
<div>
|
|
32
|
+
<span class="principle__index">01</span>
|
|
33
|
+
<p class="principle__label">Routing</p>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div>
|
|
37
|
+
<h2>Folders become URLs.</h2>
|
|
38
|
+
<p>
|
|
39
|
+
Put a page at <code>src/pages/about/index.html</code> and Nabi
|
|
40
|
+
emits the <code>/about</code> route.
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
</article>
|
|
44
|
+
|
|
45
|
+
<article class="principle">
|
|
46
|
+
<span class="principle__index">02</span>
|
|
47
|
+
<p class="principle__label">Components</p>
|
|
48
|
+
<h2>Shared once.</h2>
|
|
49
|
+
<p>
|
|
50
|
+
The same <code>ui/header</code> component is used on both pages with
|
|
51
|
+
a different <code>active</code> prop.
|
|
52
|
+
</p>
|
|
53
|
+
</article>
|
|
54
|
+
|
|
55
|
+
<article class="principle principle--accent">
|
|
56
|
+
<span class="principle__index">03</span>
|
|
57
|
+
<p class="principle__label">Runtime</p>
|
|
58
|
+
<strong>0</strong>
|
|
59
|
+
<p>No client-side component runtime is required to render this UI.</p>
|
|
60
|
+
</article>
|
|
61
|
+
</section>
|
|
62
|
+
|
|
63
|
+
<section class="back">
|
|
64
|
+
<div>
|
|
65
|
+
<p class="eyebrow">That is the whole demo</p>
|
|
66
|
+
<h2>Small enough to read in a minute.</h2>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<a href="/">
|
|
70
|
+
Back home
|
|
71
|
+
<span aria-hidden="true">↗</span>
|
|
72
|
+
</a>
|
|
73
|
+
</section>
|
|
74
|
+
</main>
|
|
75
|
+
|
|
76
|
+
<use ref="ui/footer" />
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|