create-zudo-sg 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/CHANGELOG.md +17 -0
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/bin/create-zudo-sg.js +6 -0
- package/dist/cli.d.ts +44 -0
- package/dist/cli.js +207 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/scaffold.d.ts +30 -0
- package/dist/scaffold.js +142 -0
- package/package.json +56 -0
- package/templates/default/_gitignore +4 -0
- package/templates/default/package.json +32 -0
- package/templates/default/pages/index.tsx +23 -0
- package/templates/default/pages/lib/_zudo-sg-islands.ts +3 -0
- package/templates/default/pnpm-workspace.yaml +9 -0
- package/templates/default/src/content/docs/getting-started.mdx +5 -0
- package/templates/default/src/styleguide/sg-registry.ts +10 -0
- package/templates/default/src/styles/preview-entry.css +7 -0
- package/templates/default/src/styles/ui-tokens.css +113 -0
- package/templates/default/tsconfig.json +16 -0
- package/templates/default/ui/button/button.mdx +8 -0
- package/templates/default/ui/button/button.stories.tsx +22 -0
- package/templates/default/ui/button/button.tsx +16 -0
- package/templates/default/ui/card/card.stories.tsx +17 -0
- package/templates/default/ui/card/card.tsx +12 -0
- package/templates/default/ui/counter/counter.stories.tsx +17 -0
- package/templates/default/ui/counter/counter.tsx +19 -0
- package/templates/default/zfb.config.ts +26 -0
- package/templates/default/zudo-sg.config.mjs +24 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `create-zudo-sg` are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on Keep a Changelog.
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-09-20
|
|
8
|
+
|
|
9
|
+
Initial release of the pnpm-only `create-zudo-sg` initializer.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Scaffold a private Preact + zfb host from the bundled starter template.
|
|
14
|
+
- Support project-directory prompts, npm package-name validation, `--name`,
|
|
15
|
+
`--install` / `--no-install`, `--yes`, `--help`, and `--version`.
|
|
16
|
+
- Include the working `@takazudo/zudo-sg` host configuration, example stories,
|
|
17
|
+
token CSS, generated-registry seed, and preview stylesheet entry.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Takeshi Takatsudo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# create-zudo-sg
|
|
2
|
+
|
|
3
|
+
`create-zudo-sg` is the pnpm-only initializer for a new
|
|
4
|
+
[`@takazudo/zudo-sg`](https://github.com/Takazudo/zudo-sg/tree/main/packages/styleguide)
|
|
5
|
+
styleguide host. It copies a small, working Preact + zfb project into a new
|
|
6
|
+
directory; the generated project is private and is ready to customize.
|
|
7
|
+
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pnpm create zudo-sg@latest my-styleguide
|
|
12
|
+
cd my-styleguide
|
|
13
|
+
pnpm install
|
|
14
|
+
pnpm gen-registry
|
|
15
|
+
pnpm gen-token-manifest
|
|
16
|
+
pnpm dev
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The initializer is available from its first npm release; nothing is published
|
|
20
|
+
by this epic.
|
|
21
|
+
|
|
22
|
+
The command defaults to no dependency installation and prints the five
|
|
23
|
+
commands after it creates the project. Pass `--install` when the initializer
|
|
24
|
+
should run `pnpm install` for you. The generated `package.json` pins the
|
|
25
|
+
package manager to pnpm, and this release has no npm, yarn, or `--pm` mode.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```txt
|
|
30
|
+
create-zudo-sg [project-dir] [options]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Option | Description |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `[project-dir]` | Directory to create. If omitted, the CLI prompts when it has a TTY. |
|
|
36
|
+
| `--name <pkg-name>` | Package name written to the generated `package.json`; defaults to the directory basename. |
|
|
37
|
+
| `--install` | Run `pnpm install` after copying the template. |
|
|
38
|
+
| `--no-install` | Do not install dependencies (the default). |
|
|
39
|
+
| `--yes` | Do not prompt. A project directory is required with this flag. |
|
|
40
|
+
| `--help` | Print usage and options. |
|
|
41
|
+
| `--version` | Print the initializer version. |
|
|
42
|
+
|
|
43
|
+
The target directory must be empty or not exist. The package name must follow
|
|
44
|
+
npm's lowercase package-name rules; scoped names are accepted with
|
|
45
|
+
`--name @scope/project`. The initializer does not add feature prompts or
|
|
46
|
+
overwrite an existing project.
|
|
47
|
+
|
|
48
|
+
## What the scaffold contains
|
|
49
|
+
|
|
50
|
+
The template is a minimal host, not a copy of this repository's full site. It
|
|
51
|
+
contains:
|
|
52
|
+
|
|
53
|
+
- `zfb.config.ts` composing `zudoDoc()` and `withZudoSg()` with a root base.
|
|
54
|
+
- `zudo-sg.config.mjs` with a local `ui/` components root, registry output,
|
|
55
|
+
preview stylesheet, category order, and token-manifest inputs.
|
|
56
|
+
- `pages/index.tsx` and an optional `pages/lib/_zudo-sg-islands.ts` import
|
|
57
|
+
shim for the host page.
|
|
58
|
+
- Three small Preact examples under `ui/`: Button, Card, and Counter, with
|
|
59
|
+
co-located stories and a Button MDX document.
|
|
60
|
+
- `src/styleguide/sg-registry.ts`, an empty generated seed. Run
|
|
61
|
+
`pnpm gen-registry` after changing stories.
|
|
62
|
+
- `src/content/docs/getting-started.mdx`, a seed page for the generated host's
|
|
63
|
+
documentation route.
|
|
64
|
+
- `src/styles/preview-entry.css` and `src/styles/ui-tokens.css`, which provide
|
|
65
|
+
the standalone preview stylesheet and the token source used by
|
|
66
|
+
`pnpm gen-token-manifest`.
|
|
67
|
+
- `tsconfig.json` and `pnpm-workspace.yaml`, a workspace policy suitable for a
|
|
68
|
+
fresh host.
|
|
69
|
+
|
|
70
|
+
The initializer replaces the package-name placeholder in the template and
|
|
71
|
+
renames the package-safe `_gitignore` to `.gitignore`. The token manifest is
|
|
72
|
+
generated after installation; it is intentionally not checked into the
|
|
73
|
+
template seed.
|
|
74
|
+
|
|
75
|
+
## Host requirements and caveats
|
|
76
|
+
|
|
77
|
+
The starter targets Preact + zfb hosts. Keep these dependencies in a host
|
|
78
|
+
that adopts the engine:
|
|
79
|
+
|
|
80
|
+
- `@takazudo/zdtp` is required by the injected `/tokens` route at build time.
|
|
81
|
+
- `diff` and `katex` are required by the published zudo-doc route dist that
|
|
82
|
+
the host loads, even when the corresponding optional features are disabled.
|
|
83
|
+
|
|
84
|
+
Keep the `tokens` block in `zudo-sg.config.mjs` as well. The initializer's
|
|
85
|
+
configuration is typed with `tokens` required, and `gen-token-manifest` needs
|
|
86
|
+
its two CSS paths and `manifestOut`; removing it makes the printed setup step
|
|
87
|
+
fail and leaves the token dashboards without their manifest.
|
|
88
|
+
|
|
89
|
+
For a host that already exists, use the manual composition and dependency
|
|
90
|
+
steps in the [`@takazudo/zudo-sg` installation guide](https://github.com/Takazudo/zudo-sg/tree/main/packages/styleguide#installation)
|
|
91
|
+
instead of copying this template by hand.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const VERSION = "0.1.0";
|
|
2
|
+
export interface CliArgs {
|
|
3
|
+
destination?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
install?: boolean;
|
|
6
|
+
yes?: boolean;
|
|
7
|
+
help?: boolean;
|
|
8
|
+
version?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class CliUsageError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse the small public CLI surface. `node:util` owns option parsing; the
|
|
15
|
+
* tiny normalization step only provides the conventional `--no-install`,
|
|
16
|
+
* `-h`, `-v`, and `-y` spellings that parseArgs does not expand itself.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseArgs(argv?: string[]): CliArgs;
|
|
19
|
+
export declare function formatNextSteps(projectDir: string): string;
|
|
20
|
+
export declare function printNextSteps(projectDir: string, output?: (message: string) => void): void;
|
|
21
|
+
export declare function printHelp(output?: (message: string) => void): void;
|
|
22
|
+
export declare function validateArgs(args: CliArgs): string | null;
|
|
23
|
+
export interface RunOptions {
|
|
24
|
+
argv?: string[];
|
|
25
|
+
/** Override the template for tests or another embedded template. */
|
|
26
|
+
templateDir?: string;
|
|
27
|
+
/** Resolve relative project paths from this directory. */
|
|
28
|
+
cwd?: string;
|
|
29
|
+
/** Output callback, kept injectable for unit tests. */
|
|
30
|
+
output?: (message: string) => void;
|
|
31
|
+
/** Input/output streams used by the missing-directory prompt. */
|
|
32
|
+
input?: NodeJS.ReadableStream;
|
|
33
|
+
outputStream?: NodeJS.WritableStream;
|
|
34
|
+
/** Tests may explicitly enable the prompt without relying on TTY state. */
|
|
35
|
+
interactive?: boolean;
|
|
36
|
+
/** Override installation for tests. */
|
|
37
|
+
install?: (targetDir: string) => void | Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/** Execute the initializer and return the absolute generated directory. */
|
|
40
|
+
export declare function run(options?: RunOptions): Promise<string | undefined>;
|
|
41
|
+
export declare function promptForProjectDirectory(input?: NodeJS.ReadableStream, outputStream?: NodeJS.WritableStream): Promise<string>;
|
|
42
|
+
export declare function installDependencies(targetDir: string): void;
|
|
43
|
+
/** CLI entry point used by bin/create-zudo-sg.js. */
|
|
44
|
+
export declare function main(argv?: string[]): Promise<number>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { basename, resolve } from "node:path";
|
|
4
|
+
import { stdin, stdout } from "node:process";
|
|
5
|
+
import { parseArgs as parseNodeArgs } from "node:util";
|
|
6
|
+
import { DEFAULT_TEMPLATE_DIR, scaffold, validateProjectName, } from "./scaffold.js";
|
|
7
|
+
export const VERSION = "0.1.0";
|
|
8
|
+
export class CliUsageError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "CliUsageError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse the small public CLI surface. `node:util` owns option parsing; the
|
|
16
|
+
* tiny normalization step only provides the conventional `--no-install`,
|
|
17
|
+
* `-h`, `-v`, and `-y` spellings that parseArgs does not expand itself.
|
|
18
|
+
*/
|
|
19
|
+
export function parseArgs(argv = process.argv.slice(2)) {
|
|
20
|
+
const normalizedArgv = normalizeAliases(argv);
|
|
21
|
+
const parsed = parseNodeArgs({
|
|
22
|
+
args: normalizedArgv,
|
|
23
|
+
options: {
|
|
24
|
+
help: { type: "boolean" },
|
|
25
|
+
install: { type: "boolean" },
|
|
26
|
+
name: { type: "string" },
|
|
27
|
+
noInstall: { type: "boolean" },
|
|
28
|
+
version: { type: "boolean" },
|
|
29
|
+
yes: { type: "boolean" },
|
|
30
|
+
},
|
|
31
|
+
allowPositionals: true,
|
|
32
|
+
strict: true,
|
|
33
|
+
});
|
|
34
|
+
if (parsed.positionals.length > 1) {
|
|
35
|
+
throw new CliUsageError(`Expected at most one project directory, received ${parsed.positionals.length}`);
|
|
36
|
+
}
|
|
37
|
+
const values = parsed.values;
|
|
38
|
+
const args = {};
|
|
39
|
+
const [destination] = parsed.positionals;
|
|
40
|
+
if (destination !== undefined)
|
|
41
|
+
args.destination = destination;
|
|
42
|
+
if (typeof values.name === "string")
|
|
43
|
+
args.name = values.name;
|
|
44
|
+
if (values.help === true)
|
|
45
|
+
args.help = true;
|
|
46
|
+
if (values.version === true)
|
|
47
|
+
args.version = true;
|
|
48
|
+
if (values.yes === true)
|
|
49
|
+
args.yes = true;
|
|
50
|
+
// Keep the last install spelling when both are supplied. This mirrors the
|
|
51
|
+
// way most command-line tools handle repeated switches and makes the
|
|
52
|
+
// explicit `--no-install` contract unambiguous.
|
|
53
|
+
let install;
|
|
54
|
+
let afterTerminator = false;
|
|
55
|
+
for (const arg of argv) {
|
|
56
|
+
if (arg === "--") {
|
|
57
|
+
afterTerminator = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (afterTerminator)
|
|
61
|
+
continue;
|
|
62
|
+
if (arg === "--install")
|
|
63
|
+
install = true;
|
|
64
|
+
if (arg === "--no-install")
|
|
65
|
+
install = false;
|
|
66
|
+
}
|
|
67
|
+
if (install !== undefined)
|
|
68
|
+
args.install = install;
|
|
69
|
+
return args;
|
|
70
|
+
}
|
|
71
|
+
function normalizeAliases(argv) {
|
|
72
|
+
let afterTerminator = false;
|
|
73
|
+
return argv.map((arg) => {
|
|
74
|
+
if (arg === "--") {
|
|
75
|
+
afterTerminator = true;
|
|
76
|
+
return arg;
|
|
77
|
+
}
|
|
78
|
+
if (afterTerminator)
|
|
79
|
+
return arg;
|
|
80
|
+
if (arg === "--no-install")
|
|
81
|
+
return "--noInstall";
|
|
82
|
+
if (arg === "-h")
|
|
83
|
+
return "--help";
|
|
84
|
+
if (arg === "-v")
|
|
85
|
+
return "--version";
|
|
86
|
+
if (arg === "-y")
|
|
87
|
+
return "--yes";
|
|
88
|
+
return arg;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
export function formatNextSteps(projectDir) {
|
|
92
|
+
return [
|
|
93
|
+
`cd ${shellEscapePath(projectDir)}`,
|
|
94
|
+
"pnpm install",
|
|
95
|
+
"pnpm gen-registry",
|
|
96
|
+
"pnpm gen-token-manifest",
|
|
97
|
+
"pnpm dev",
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
100
|
+
function shellEscapePath(projectDir) {
|
|
101
|
+
if (!/[\s'"`$\\]/.test(projectDir))
|
|
102
|
+
return projectDir;
|
|
103
|
+
return `'${projectDir.replaceAll("'", "'\\''")}'`;
|
|
104
|
+
}
|
|
105
|
+
export function printNextSteps(projectDir, output = console.log) {
|
|
106
|
+
output(formatNextSteps(projectDir));
|
|
107
|
+
}
|
|
108
|
+
export function printHelp(output = console.log) {
|
|
109
|
+
output(`Usage: create-zudo-sg [project-dir] [options]
|
|
110
|
+
|
|
111
|
+
Create a new pnpm-based zudo-sg styleguide project.
|
|
112
|
+
|
|
113
|
+
Options:
|
|
114
|
+
--name <pkg-name> Package name written to package.json
|
|
115
|
+
--install Run pnpm install after scaffolding
|
|
116
|
+
--no-install Do not install dependencies (the default)
|
|
117
|
+
--yes Do not prompt for a missing project directory
|
|
118
|
+
--help Show this help message
|
|
119
|
+
--version Show the package version
|
|
120
|
+
`);
|
|
121
|
+
}
|
|
122
|
+
export function validateArgs(args) {
|
|
123
|
+
if (args.name !== undefined)
|
|
124
|
+
return validateProjectName(args.name);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
/** Execute the initializer and return the absolute generated directory. */
|
|
128
|
+
export async function run(options = {}) {
|
|
129
|
+
const args = parseArgs(options.argv);
|
|
130
|
+
const output = options.output ?? console.log;
|
|
131
|
+
if (args.help) {
|
|
132
|
+
printHelp(output);
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
if (args.version) {
|
|
136
|
+
output(VERSION);
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const input = options.input ?? stdin;
|
|
140
|
+
const outputStream = options.outputStream ?? stdout;
|
|
141
|
+
let displayDestination = args.destination?.trim();
|
|
142
|
+
if (!displayDestination) {
|
|
143
|
+
const interactive = options.interactive ?? Boolean(hasTTY(input) && hasTTY(outputStream));
|
|
144
|
+
if (args.yes) {
|
|
145
|
+
throw new CliUsageError("A project directory is required when --yes is provided");
|
|
146
|
+
}
|
|
147
|
+
if (!interactive) {
|
|
148
|
+
throw new CliUsageError("A project directory is required in non-interactive mode");
|
|
149
|
+
}
|
|
150
|
+
displayDestination = await promptForProjectDirectory(input, outputStream);
|
|
151
|
+
if (!displayDestination) {
|
|
152
|
+
throw new CliUsageError("A project directory is required");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const targetDir = resolve(options.cwd ?? process.cwd(), displayDestination);
|
|
156
|
+
const projectName = args.name ?? basename(targetDir);
|
|
157
|
+
const nameError = validateArgs({ ...args, name: projectName });
|
|
158
|
+
if (nameError)
|
|
159
|
+
throw new CliUsageError(`Invalid project name: ${nameError}`);
|
|
160
|
+
const scaffoldOptions = {
|
|
161
|
+
targetDir,
|
|
162
|
+
projectName,
|
|
163
|
+
templateDir: options.templateDir ?? DEFAULT_TEMPLATE_DIR,
|
|
164
|
+
};
|
|
165
|
+
await scaffold(scaffoldOptions);
|
|
166
|
+
if (args.install === true) {
|
|
167
|
+
if (options.install) {
|
|
168
|
+
await options.install(targetDir);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
installDependencies(targetDir);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
output(`Created ${displayDestination}`);
|
|
175
|
+
printNextSteps(displayDestination, output);
|
|
176
|
+
return targetDir;
|
|
177
|
+
}
|
|
178
|
+
export async function promptForProjectDirectory(input = stdin, outputStream = stdout) {
|
|
179
|
+
const readline = createInterface({ input, output: outputStream });
|
|
180
|
+
try {
|
|
181
|
+
return (await readline.question("Project directory: ")).trim();
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
readline.close();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export function installDependencies(targetDir) {
|
|
188
|
+
execFileSync("pnpm", ["install"], {
|
|
189
|
+
cwd: targetDir,
|
|
190
|
+
stdio: "inherit",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function hasTTY(stream) {
|
|
194
|
+
return "isTTY" in stream && stream.isTTY === true;
|
|
195
|
+
}
|
|
196
|
+
/** CLI entry point used by bin/create-zudo-sg.js. */
|
|
197
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
198
|
+
try {
|
|
199
|
+
await run({ argv });
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
+
console.error(`Error: ${message}`);
|
|
205
|
+
return 1;
|
|
206
|
+
}
|
|
207
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { DEFAULT_TEMPLATE_DIR, isValidPackageName, scaffold, validatePackageName, validateProjectName, type ScaffoldOptions, } from "./scaffold.js";
|
|
2
|
+
export { VERSION, formatNextSteps, installDependencies, main, parseArgs, printHelp, printNextSteps, promptForProjectDirectory, run, validateArgs, type CliArgs, type RunOptions, } from "./cli.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { DEFAULT_TEMPLATE_DIR, isValidPackageName, scaffold, validatePackageName, validateProjectName, } from "./scaffold.js";
|
|
2
|
+
export { VERSION, formatNextSteps, installDependencies, main, parseArgs, printHelp, printNextSteps, promptForProjectDirectory, run, validateArgs, } from "./cli.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The package template shipped by create-zudo-sg. */
|
|
2
|
+
export declare const DEFAULT_TEMPLATE_DIR: string;
|
|
3
|
+
export interface ScaffoldOptions {
|
|
4
|
+
/** Directory to write the generated project into. */
|
|
5
|
+
targetDir: string;
|
|
6
|
+
/** Package name written to the generated package.json. */
|
|
7
|
+
projectName: string;
|
|
8
|
+
/** Template directory. Defaults to the package's templates/default tree. */
|
|
9
|
+
templateDir?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validate a package name using npm's lowercase package-name grammar.
|
|
13
|
+
*
|
|
14
|
+
* The initializer accepts scoped names for `--name`, while a directory's
|
|
15
|
+
* basename normally produces an unscoped name. Returning an error string
|
|
16
|
+
* keeps validation useful from both the CLI and the programmatic API.
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateProjectName(name: string): string | null;
|
|
19
|
+
export declare function isValidPackageName(name: string): boolean;
|
|
20
|
+
/** Compatibility-friendly name for callers that think in npm terms. */
|
|
21
|
+
export declare const validatePackageName: typeof validateProjectName;
|
|
22
|
+
/**
|
|
23
|
+
* Scaffold a project from a template directory.
|
|
24
|
+
*
|
|
25
|
+
* The explicit template parameter is intentional: unit tests can supply a
|
|
26
|
+
* tiny fixture and remain independent of the separately synchronized
|
|
27
|
+
* templates/default tree.
|
|
28
|
+
*/
|
|
29
|
+
export declare function scaffold(options: ScaffoldOptions): Promise<string>;
|
|
30
|
+
export declare function scaffold(targetDir: string, projectName: string, templateDir?: string): Promise<string>;
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { copyFile, mkdir, readFile, readdir, readlink, stat, symlink, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const PROJECT_NAME_MAX = 214;
|
|
5
|
+
const PACKAGE_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;
|
|
6
|
+
const SCOPED_PACKAGE_NAME_RE = /^@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/;
|
|
7
|
+
/** The package template shipped by create-zudo-sg. */
|
|
8
|
+
export const DEFAULT_TEMPLATE_DIR = resolveTemplateDirectory();
|
|
9
|
+
function resolveTemplateDirectory() {
|
|
10
|
+
const templateUrl = new URL("../templates/default/", import.meta.url);
|
|
11
|
+
if (templateUrl.protocol === "file:")
|
|
12
|
+
return fileURLToPath(templateUrl);
|
|
13
|
+
// Vite's root test runner rewrites import.meta.url to an HTTP URL. The
|
|
14
|
+
// package itself always runs from a file URL, but keeping this fallback
|
|
15
|
+
// makes the exported constant safe for the root vitest config too.
|
|
16
|
+
const pathname = decodeURIComponent(templateUrl.pathname).replace(/^\/\@fs\//, "/");
|
|
17
|
+
// In the monorepo's root Vite runner, module URLs are rooted at
|
|
18
|
+
// `/packages/...` rather than the filesystem root.
|
|
19
|
+
return pathname.startsWith("/packages/")
|
|
20
|
+
? resolve(process.cwd(), pathname.slice(1))
|
|
21
|
+
: pathname;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validate a package name using npm's lowercase package-name grammar.
|
|
25
|
+
*
|
|
26
|
+
* The initializer accepts scoped names for `--name`, while a directory's
|
|
27
|
+
* basename normally produces an unscoped name. Returning an error string
|
|
28
|
+
* keeps validation useful from both the CLI and the programmatic API.
|
|
29
|
+
*/
|
|
30
|
+
export function validateProjectName(name) {
|
|
31
|
+
if (name.length === 0)
|
|
32
|
+
return "Project name is required";
|
|
33
|
+
if (name.length > PROJECT_NAME_MAX) {
|
|
34
|
+
return `Project name must be ${PROJECT_NAME_MAX} characters or fewer`;
|
|
35
|
+
}
|
|
36
|
+
const valid = name.startsWith("@")
|
|
37
|
+
? SCOPED_PACKAGE_NAME_RE.test(name)
|
|
38
|
+
: PACKAGE_NAME_RE.test(name);
|
|
39
|
+
if (!valid || name === "node_modules") {
|
|
40
|
+
return ("Project name must be a valid npm package name: start with a lowercase " +
|
|
41
|
+
"letter or digit (or use @scope/name), and contain only lowercase " +
|
|
42
|
+
"letters, digits, dots, underscores, and hyphens");
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
export function isValidPackageName(name) {
|
|
47
|
+
return validateProjectName(name) === null;
|
|
48
|
+
}
|
|
49
|
+
/** Compatibility-friendly name for callers that think in npm terms. */
|
|
50
|
+
export const validatePackageName = validateProjectName;
|
|
51
|
+
async function assertEmptyTarget(targetDir) {
|
|
52
|
+
try {
|
|
53
|
+
const targetStats = await stat(targetDir);
|
|
54
|
+
if (!targetStats.isDirectory()) {
|
|
55
|
+
throw new Error(`Target path "${targetDir}" is not a directory`);
|
|
56
|
+
}
|
|
57
|
+
const entries = await readdir(targetDir);
|
|
58
|
+
if (entries.length > 0) {
|
|
59
|
+
throw new Error(`Directory "${targetDir}" already exists and is not empty`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (isMissingPathError(error))
|
|
64
|
+
return;
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function assertDirectory(directory, label) {
|
|
69
|
+
let directoryStats;
|
|
70
|
+
try {
|
|
71
|
+
directoryStats = await stat(directory);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (isMissingPathError(error)) {
|
|
75
|
+
throw new Error(`${label} does not exist: "${directory}"`);
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
if (!directoryStats.isDirectory()) {
|
|
80
|
+
throw new Error(`${label} is not a directory: "${directory}"`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isMissingPathError(error) {
|
|
84
|
+
return (typeof error === "object" &&
|
|
85
|
+
error !== null &&
|
|
86
|
+
"code" in error &&
|
|
87
|
+
error.code === "ENOENT");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Copy a template tree while translating `_gitignore` to `.gitignore`.
|
|
91
|
+
*
|
|
92
|
+
* npm omits `.gitignore` from published package tarballs, so the committed
|
|
93
|
+
* template uses `_gitignore` and the rename happens only while scaffolding.
|
|
94
|
+
*/
|
|
95
|
+
async function copyTemplateTree(sourceDir, targetDir) {
|
|
96
|
+
await mkdir(targetDir, { recursive: true });
|
|
97
|
+
const entries = await readdir(sourceDir, { withFileTypes: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const sourcePath = join(sourceDir, entry.name);
|
|
100
|
+
const targetName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
|
|
101
|
+
const targetPath = join(targetDir, targetName);
|
|
102
|
+
if (entry.isDirectory()) {
|
|
103
|
+
await copyTemplateTree(sourcePath, targetPath);
|
|
104
|
+
}
|
|
105
|
+
else if (entry.isSymbolicLink()) {
|
|
106
|
+
await symlink(await readlink(sourcePath), targetPath);
|
|
107
|
+
}
|
|
108
|
+
else if (entry.isFile()) {
|
|
109
|
+
await copyFile(sourcePath, targetPath);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
throw new Error(`Unsupported template entry: "${sourcePath}"`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export async function scaffold(optionsOrTargetDir, projectName, templateDir = DEFAULT_TEMPLATE_DIR) {
|
|
117
|
+
const options = typeof optionsOrTargetDir === "string"
|
|
118
|
+
? {
|
|
119
|
+
targetDir: optionsOrTargetDir,
|
|
120
|
+
projectName: projectName ?? basename(resolve(optionsOrTargetDir)),
|
|
121
|
+
templateDir,
|
|
122
|
+
}
|
|
123
|
+
: optionsOrTargetDir;
|
|
124
|
+
const nameError = validateProjectName(options.projectName);
|
|
125
|
+
if (nameError)
|
|
126
|
+
throw new Error(`Invalid project name: ${nameError}`);
|
|
127
|
+
const targetDir = resolve(options.targetDir);
|
|
128
|
+
const sourceDir = resolve(options.templateDir ?? DEFAULT_TEMPLATE_DIR);
|
|
129
|
+
await assertDirectory(sourceDir, "Template directory");
|
|
130
|
+
// Validate the source before creating a target, so a malformed package
|
|
131
|
+
// cannot leave a partially generated project behind.
|
|
132
|
+
const sourcePackageJson = await readFile(join(sourceDir, "package.json"), "utf8");
|
|
133
|
+
await assertEmptyTarget(targetDir);
|
|
134
|
+
await mkdir(targetDir, { recursive: true });
|
|
135
|
+
await copyTemplateTree(sourceDir, targetDir);
|
|
136
|
+
const targetPackageJson = join(targetDir, "package.json");
|
|
137
|
+
// Preserve the template's formatting and any comments-like spacing while
|
|
138
|
+
// replacing every occurrence of the one scaffold token.
|
|
139
|
+
const packageJson = sourcePackageJson.replaceAll("__PROJECT_NAME__", options.projectName);
|
|
140
|
+
await writeFile(targetPackageJson, packageJson);
|
|
141
|
+
return targetDir;
|
|
142
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-zudo-sg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a new zudo-sg styleguide project",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Takeshi Takatsudo",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Takazudo/zudo-sg.git",
|
|
10
|
+
"directory": "packages/create-zudo-sg"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Takazudo/zudo-sg/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"create",
|
|
17
|
+
"scaffold",
|
|
18
|
+
"styleguide",
|
|
19
|
+
"zudo-sg",
|
|
20
|
+
"cli"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=22"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"provenance": true
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"bin": {
|
|
31
|
+
"create-zudo-sg": "bin/create-zudo-sg.js"
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.js",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"bin",
|
|
39
|
+
"dist",
|
|
40
|
+
"templates",
|
|
41
|
+
"!dist/__tests__",
|
|
42
|
+
"CHANGELOG.md"
|
|
43
|
+
],
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.0.0",
|
|
46
|
+
"typescript": "^5.9.0",
|
|
47
|
+
"vitest": "^4.1.9"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
51
|
+
"build": "pnpm clean && tsc",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"check": "pnpm typecheck && pnpm build && pnpm test"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@11.5.2",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "zfb dev",
|
|
9
|
+
"build": "zfb build",
|
|
10
|
+
"check": "tsc --noEmit -p tsconfig.json",
|
|
11
|
+
"gen-registry": "zudo-sg gen-registry",
|
|
12
|
+
"gen-token-manifest": "zudo-sg gen-token-manifest"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@takazudo/zdtp": "0.8.0",
|
|
16
|
+
"@takazudo/zfb": "2.19.0",
|
|
17
|
+
"@takazudo/zfb-md-wasm": "2.19.0",
|
|
18
|
+
"@takazudo/zfb-runtime": "2.19.0",
|
|
19
|
+
"@takazudo/zudo-doc": "5.26.0",
|
|
20
|
+
"@takazudo/zudo-sg": "^0.2.0",
|
|
21
|
+
"diff": "^8.0.4",
|
|
22
|
+
"katex": "^0.16.38",
|
|
23
|
+
"preact": "^10.29.1",
|
|
24
|
+
"preact-render-to-string": "^6.6.6",
|
|
25
|
+
"tailwindcss": "^4.2.0",
|
|
26
|
+
"zod": "^4.3.6"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"typescript": "^5.9.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// The root page stays host-owned so it can import site-wide islands.
|
|
4
|
+
import type { JSX } from "preact";
|
|
5
|
+
// Optional islands seed; see pages/lib/_zudo-sg-islands.ts.
|
|
6
|
+
import "./lib/_zudo-sg-islands";
|
|
7
|
+
|
|
8
|
+
export const frontmatter = { title: "Styleguide starter" };
|
|
9
|
+
|
|
10
|
+
export default function IndexPage(): JSX.Element {
|
|
11
|
+
return (
|
|
12
|
+
<html lang="en">
|
|
13
|
+
<head>
|
|
14
|
+
<meta charSet="utf-8" />
|
|
15
|
+
<title>Styleguide starter</title>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<h1 data-host-index>Styleguide starter</h1>
|
|
19
|
+
<a href="/components">Components</a>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GENERATED by `zudo-sg gen-registry` — seed file. Run `pnpm gen-registry`
|
|
2
|
+
// after changing stories; the command rewrites the block below from
|
|
3
|
+
// ui/*.stories.tsx. This seed only needs the BEGIN/END markers present once.
|
|
4
|
+
// GENERATED:SG_REGISTRY_BEGIN — do not hand-edit; run `pnpm gen-registry`.
|
|
5
|
+
import type { StoryModule } from "@takazudo/zudo-sg/stories";
|
|
6
|
+
|
|
7
|
+
export const storyModules: Record<string, StoryModule> = {};
|
|
8
|
+
|
|
9
|
+
export const storyExportOrder: Record<string, string[]> = {};
|
|
10
|
+
// GENERATED:SG_REGISTRY_END
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* Host `previewStyles` entry — compiled standalone by
|
|
2
|
+
@takazudo/zudo-sg/plugins/preview-css. Preflight + utilities + this
|
|
3
|
+
project's own token sheet + an @source for the ui/ corpus. */
|
|
4
|
+
@import "tailwindcss/preflight";
|
|
5
|
+
@import "tailwindcss/utilities";
|
|
6
|
+
@import "./ui-tokens.css";
|
|
7
|
+
@source "../../ui";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* Self-contained input for the packed gen-token-manifest CLI. The current
|
|
2
|
+
* generator requires this complete vocabulary; none comes from demo-ui or
|
|
3
|
+
* the workspace. The Button/Card still use only the original five tokens. */
|
|
4
|
+
:root {
|
|
5
|
+
--palette-neutral-0: #ffffff;
|
|
6
|
+
--palette-neutral-1: #e2e8f0;
|
|
7
|
+
--palette-neutral-2: #475569;
|
|
8
|
+
--palette-neutral-3: #0f172a;
|
|
9
|
+
--palette-accent-0: #dbeafe;
|
|
10
|
+
--palette-accent-1: #93c5fd;
|
|
11
|
+
--palette-accent-2: #2563eb;
|
|
12
|
+
--palette-accent-3: #1e40af;
|
|
13
|
+
--palette-state-danger: #dc2626;
|
|
14
|
+
--palette-state-danger-dark: #f87171;
|
|
15
|
+
--palette-state-success: #15803d;
|
|
16
|
+
--palette-state-success-dark: #4ade80;
|
|
17
|
+
--palette-state-warning: #a16207;
|
|
18
|
+
--palette-state-warning-dark: #facc15;
|
|
19
|
+
--palette-state-info: #0369a1;
|
|
20
|
+
--palette-state-info-dark: #38bdf8;
|
|
21
|
+
--palette-line-vacuum-accent: #2563eb;
|
|
22
|
+
--palette-line-vacuum-accent-dark: #93c5fd;
|
|
23
|
+
--palette-line-vacuum-hover: #1e40af;
|
|
24
|
+
--palette-line-vacuum-hover-dark: #dbeafe;
|
|
25
|
+
--palette-line-process-accent: #2563eb;
|
|
26
|
+
--palette-line-process-accent-dark: #93c5fd;
|
|
27
|
+
--palette-line-process-hover: #1e40af;
|
|
28
|
+
--palette-line-process-hover-dark: #dbeafe;
|
|
29
|
+
--palette-line-laser-accent: #2563eb;
|
|
30
|
+
--palette-line-laser-accent-dark: #93c5fd;
|
|
31
|
+
--palette-line-laser-hover: #1e40af;
|
|
32
|
+
--palette-line-laser-hover-dark: #dbeafe;
|
|
33
|
+
--palette-line-meeting-accent: #2563eb;
|
|
34
|
+
--palette-line-meeting-accent-dark: #93c5fd;
|
|
35
|
+
--palette-line-meeting-hover: #1e40af;
|
|
36
|
+
--palette-line-meeting-hover-dark: #dbeafe;
|
|
37
|
+
--palette-line-beauty-accent: #2563eb;
|
|
38
|
+
--palette-line-beauty-accent-dark: #93c5fd;
|
|
39
|
+
--palette-line-beauty-hover: #1e40af;
|
|
40
|
+
--palette-line-beauty-hover-dark: #dbeafe;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@theme {
|
|
44
|
+
--color-accent: #2563eb;
|
|
45
|
+
--color-fg: #0f172a;
|
|
46
|
+
--color-bg: #ffffff;
|
|
47
|
+
--color-surface: light-dark(#ffffff, #0f172a);
|
|
48
|
+
--color-surface-2: light-dark(#e2e8f0, #475569);
|
|
49
|
+
--color-border: #475569;
|
|
50
|
+
--color-loading-scrim: #ffffff80;
|
|
51
|
+
--color-muted: #475569;
|
|
52
|
+
--color-accent-hover: #1e40af;
|
|
53
|
+
--color-on-accent: #ffffff;
|
|
54
|
+
--color-focus: #2563eb;
|
|
55
|
+
--color-rail-bg: #0f172a;
|
|
56
|
+
--color-rail-bg-strong: #0f172a;
|
|
57
|
+
--color-rail-fg: #ffffff;
|
|
58
|
+
--color-rail-muted: #e2e8f0;
|
|
59
|
+
--color-rail-border: #475569;
|
|
60
|
+
--color-rail-hover-bg: #475569;
|
|
61
|
+
--color-success: #15803d;
|
|
62
|
+
--color-danger: #dc2626;
|
|
63
|
+
--color-warning: #a16207;
|
|
64
|
+
--color-info: #0369a1;
|
|
65
|
+
|
|
66
|
+
--spacing-hsp-2xs: 0.125rem;
|
|
67
|
+
--spacing-hsp-xs: 0.25rem;
|
|
68
|
+
--spacing-hsp-sm: 0.5rem;
|
|
69
|
+
--spacing-hsp-md: 1rem;
|
|
70
|
+
--spacing-hsp-lg: 1.5rem;
|
|
71
|
+
--spacing-hsp-xl: 2rem;
|
|
72
|
+
--spacing-hsp-2xl: 3rem;
|
|
73
|
+
--spacing-vsp-3xs: 0.0625rem;
|
|
74
|
+
--spacing-vsp-2xs: 0.125rem;
|
|
75
|
+
--spacing-vsp-xs: 0.25rem;
|
|
76
|
+
--spacing-vsp-sm: 0.5rem;
|
|
77
|
+
--spacing-vsp-md: 1rem;
|
|
78
|
+
--spacing-vsp-lg: 1.5rem;
|
|
79
|
+
--spacing-vsp-xl: 2rem;
|
|
80
|
+
--spacing-vsp-2xl: 3rem;
|
|
81
|
+
|
|
82
|
+
--text-xs: 0.75rem;
|
|
83
|
+
--text-sm: 0.875rem;
|
|
84
|
+
--text-base: 1rem;
|
|
85
|
+
--text-lg: 1.125rem;
|
|
86
|
+
--text-xl: 1.25rem;
|
|
87
|
+
--text-2xl: 1.5rem;
|
|
88
|
+
--text-xs--line-height: 1.5;
|
|
89
|
+
--text-sm--line-height: 1.5;
|
|
90
|
+
--text-base--line-height: 1.5;
|
|
91
|
+
--text-lg--line-height: 1.5;
|
|
92
|
+
--text-xl--line-height: 1.5;
|
|
93
|
+
--text-2xl--line-height: 1.5;
|
|
94
|
+
--font-weight-normal: 400;
|
|
95
|
+
--font-weight-medium: 500;
|
|
96
|
+
--font-weight-semibold: 600;
|
|
97
|
+
--font-weight-bold: 700;
|
|
98
|
+
--leading-tight: 1.25;
|
|
99
|
+
--leading-snug: 1.375;
|
|
100
|
+
--leading-normal: 1.5;
|
|
101
|
+
--leading-relaxed: 1.625;
|
|
102
|
+
--font-sans: sans-serif;
|
|
103
|
+
--font-mono: monospace;
|
|
104
|
+
|
|
105
|
+
--radius-DEFAULT: 0.25rem;
|
|
106
|
+
--radius-sm: 0.25rem;
|
|
107
|
+
--radius-md: 0.5rem;
|
|
108
|
+
--radius-lg: 1rem;
|
|
109
|
+
--radius-full: 9999px;
|
|
110
|
+
--shadow-card: 0 1px 2px #0000001a;
|
|
111
|
+
--shadow-raised: 0 2px 4px #0000001a;
|
|
112
|
+
--shadow-overlay: 0 4px 8px #0000001a;
|
|
113
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"allowImportingTsExtensions": true,
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"checkJs": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"jsxImportSource": "preact",
|
|
13
|
+
"skipLibCheck": false
|
|
14
|
+
},
|
|
15
|
+
"include": ["pages", "src", "ui", "zfb.config.ts", "zudo-sg.config.mjs"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { Story, StoryMeta } from "@takazudo/zudo-sg/stories";
|
|
4
|
+
import { Button } from "./button.tsx";
|
|
5
|
+
|
|
6
|
+
const meta: StoryMeta = {
|
|
7
|
+
title: "Button",
|
|
8
|
+
category: "Actions",
|
|
9
|
+
description: "A component rendered through the styleguide engine.",
|
|
10
|
+
usage: `import { Button } from "./ui/button/button";\n\n<Button>Primary action</Button>`,
|
|
11
|
+
};
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const Primary: Story = {
|
|
15
|
+
name: "Primary",
|
|
16
|
+
render: () => <Button>Primary action</Button>,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const Plain: Story = {
|
|
20
|
+
name: "Plain",
|
|
21
|
+
render: () => <Button tone="plain">Plain action</Button>,
|
|
22
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { ComponentChildren, JSX } from "preact";
|
|
4
|
+
|
|
5
|
+
export function Button(props: { children: ComponentChildren; tone?: "accent" | "plain" }): JSX.Element {
|
|
6
|
+
const tone = props.tone ?? "accent";
|
|
7
|
+
return (
|
|
8
|
+
<button
|
|
9
|
+
type="button"
|
|
10
|
+
data-ui-button={tone}
|
|
11
|
+
class={tone === "accent" ? "bg-accent text-bg px-hsp-md py-vsp-sm" : "text-fg px-hsp-md"}
|
|
12
|
+
>
|
|
13
|
+
{props.children}
|
|
14
|
+
</button>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { Story, StoryMeta } from "@takazudo/zudo-sg/stories";
|
|
4
|
+
import { Card } from "./card.tsx";
|
|
5
|
+
|
|
6
|
+
const meta: StoryMeta = {
|
|
7
|
+
title: "Card",
|
|
8
|
+
category: "Layout",
|
|
9
|
+
description: "A second real component, proving the registry discovers more than one story file.",
|
|
10
|
+
usage: `import { Card } from "./ui/card/card";\n\n<Card title="Example">Body</Card>`,
|
|
11
|
+
};
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const Basic: Story = {
|
|
15
|
+
name: "Basic",
|
|
16
|
+
render: () => <Card title="Example">Body content</Card>,
|
|
17
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { ComponentChildren, JSX } from "preact";
|
|
4
|
+
|
|
5
|
+
export function Card(props: { title: string; children: ComponentChildren }): JSX.Element {
|
|
6
|
+
return (
|
|
7
|
+
<div data-ui-card class="border border-fg/20 rounded-md p-hsp-md">
|
|
8
|
+
<h3 class="font-bold mb-vsp-sm">{props.title}</h3>
|
|
9
|
+
{props.children}
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { Story, StoryMeta } from "@takazudo/zudo-sg/stories";
|
|
4
|
+
import Counter from "./counter.tsx";
|
|
5
|
+
|
|
6
|
+
const meta: StoryMeta = {
|
|
7
|
+
title: "Counter",
|
|
8
|
+
category: "Actions",
|
|
9
|
+
description: "A `\"use client\"` island reached through the generated registry.",
|
|
10
|
+
usage: `import Counter from "./ui/counter/counter";\n\n<Counter start={3} />`,
|
|
11
|
+
};
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const Basic: Story = {
|
|
15
|
+
name: "Basic",
|
|
16
|
+
render: () => <Counter start={3} />,
|
|
17
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/** @jsxRuntime automatic */
|
|
3
|
+
/** @jsxImportSource preact */
|
|
4
|
+
|
|
5
|
+
// Real `"use client"` island reached through the generated registry. It is
|
|
6
|
+
// intentionally not statically imported by a host `pages/` file.
|
|
7
|
+
|
|
8
|
+
import { useState } from "preact/hooks";
|
|
9
|
+
|
|
10
|
+
export default function Counter(props: { start?: number }) {
|
|
11
|
+
const [count, setCount] = useState(props.start ?? 0);
|
|
12
|
+
return (
|
|
13
|
+
<button type="button" data-counter onClick={() => setCount((c) => c + 1)}>
|
|
14
|
+
count: {count}
|
|
15
|
+
</button>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
Counter.displayName = "Counter";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// zfb.config.ts — styleguide starter configuration.
|
|
2
|
+
//
|
|
3
|
+
// This minimal zudo-doc site demonstrates a host application. `withZudoSg`
|
|
4
|
+
// appends the engine's plugins and collections after `zudoDoc()`'s own — the
|
|
5
|
+
// routes plugin requires `packageOwnedRoutes: true`, which `zudoDoc()`
|
|
6
|
+
// defaults on.
|
|
7
|
+
import { defineConfig } from "@takazudo/zfb/config";
|
|
8
|
+
import { zudoDoc } from "@takazudo/zudo-doc/config";
|
|
9
|
+
import { withZudoSg } from "@takazudo/zudo-sg/config";
|
|
10
|
+
import zudoSgConfig from "./zudo-sg.config.mjs";
|
|
11
|
+
|
|
12
|
+
export default defineConfig(
|
|
13
|
+
withZudoSg(
|
|
14
|
+
zudoDoc({
|
|
15
|
+
siteName: "Styleguide Starter",
|
|
16
|
+
base: "/",
|
|
17
|
+
port: 4397,
|
|
18
|
+
// No mermaid diagrams anywhere in this starter's one seed doc; turned
|
|
19
|
+
// off rather than leaving the default on to keep the dependency set
|
|
20
|
+
// minimal (see package.json).
|
|
21
|
+
mermaid: false,
|
|
22
|
+
strictContentBridge: true,
|
|
23
|
+
}),
|
|
24
|
+
zudoSgConfig,
|
|
25
|
+
),
|
|
26
|
+
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// zudo-sg.config.mjs — styleguide starter configuration.
|
|
2
|
+
//
|
|
3
|
+
// `componentsRoots[0].importBase` is a RELATIVE path ("../../ui", from this
|
|
4
|
+
// config's registryOut directory `src/styleguide/` back up to the project
|
|
5
|
+
// root's `ui/`) rather than a package name: this starter has no installed UI
|
|
6
|
+
// provider package, only its own `ui/` corpus, so the generated registry's
|
|
7
|
+
// `import * as … from "../../ui/<slug>.stories.tsx"` resolves as a plain
|
|
8
|
+
// relative import. `uiPackageName` points at the engine's own `stories`
|
|
9
|
+
// subpath (the actual origin of `StoryModule`) since there is no separate
|
|
10
|
+
// provider package to attribute stories to.
|
|
11
|
+
|
|
12
|
+
/** @satisfies {import("@takazudo/zudo-sg/config").ZudoSgComposeOptions} */
|
|
13
|
+
export default {
|
|
14
|
+
componentsRoots: [{ dir: "ui", importBase: "../../ui" }],
|
|
15
|
+
registryOut: "./src/styleguide/sg-registry.ts",
|
|
16
|
+
categoryOrder: ["Actions", "Layout"],
|
|
17
|
+
uiPackageName: "@takazudo/zudo-sg/stories",
|
|
18
|
+
barrelIndex: null,
|
|
19
|
+
previewStyles: "./src/styles/preview-entry.css",
|
|
20
|
+
tokens: {
|
|
21
|
+
cssFiles: ["./src/styles/ui-tokens.css", "./src/styles/ui-tokens.css"],
|
|
22
|
+
manifestOut: "./src/styleguide/token-manifest.ts",
|
|
23
|
+
},
|
|
24
|
+
};
|