swatchkit 0.7.0 → 0.8.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.
Files changed (3) hide show
  1. package/README.md +23 -12
  2. package/build.js +192 -146
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -201,10 +201,17 @@ SwatchKit automatically bundles your JS files, wraps them in a safety scope (IIF
201
201
  Understanding the build pipeline helps you know which files to edit and which are generated.
202
202
 
203
203
  ### 1. `swatchkit init` (Scaffolding)
204
- Copies "blueprints" into your project to get you started.
204
+ Copies "blueprints" into your project to get you started. Init tracks a manifest of every file it manages (token JSONs, CSS blueprints, layout templates) so it can report what's new, changed, or up to date.
205
+
206
+ * **Fresh project:** Creates directories and copies all blueprint files.
207
+ * **Already initialized:** Prints a status report comparing your files to the latest blueprints. Suggests `--force` if anything has changed.
208
+ * **`--force`:** Overwrites all init-managed files with the latest blueprints. Your custom swatch HTML files and any CSS files without blueprint counterparts are never touched.
209
+ * **`--dry-run`:** Shows what would happen without writing anything.
210
+
211
+ Files created:
205
212
  * **`tokens/*.json`**: These are your **Source of Truth**. You edit these files.
206
- * **`css/`**: Copies static CSS files (`main.css`, `reset.css`, etc.). **You own these files**.
207
- * **`swatchkit/`**: Sets up the documentation structure and layout.
213
+ * **`css/`**: Copies static CSS files (`main.css`, `reset.css`, compositions, etc.). **You own these files**.
214
+ * **`swatchkit/`**: Sets up the documentation structure, layout templates, and token display patterns.
208
215
 
209
216
  ### 2. `swatchkit` (Build Process)
210
217
  Compiles your documentation site into `dist/swatchkit/`.
@@ -228,7 +235,8 @@ SwatchKit includes sensible defaults in `css/variables.css` and `css/global-styl
228
235
  | `css/main.css` | ✅ **YES** | Your entry point. Safe. |
229
236
  | `css/global-styles.css` | ✅ **YES** | You own this. Manually update if you rename tokens. |
230
237
  | `css/tokens.css` | 🚫 **NO** | Overwritten by **every** `swatchkit build` and `swatchkit init`. |
231
- | `swatchkit/_layout.html`| ✅ **YES** | Safe. **Warning:** `swatchkit init --force` WILL overwrite this. |
238
+ | `swatchkit/_layout.html`| ✅ **YES** | Safe during normal use. `init --force` overwrites all init-managed files, including this one. |
239
+ | `swatchkit/_preview.html`| ✅ **YES** | Same as `_layout.html` — safe unless you run `init --force`. |
232
240
  | `swatchkit/tokens/*.html`| 🚫 **NO** | Overwritten by `swatchkit build` (visual previews). |
233
241
 
234
242
  ## CLI Reference
@@ -240,17 +248,20 @@ swatchkit [command] [options]
240
248
  ### Commands
241
249
 
242
250
  - `swatchkit` (Default): Builds the library.
243
- - `swatchkit init`: Scaffolds the layout and token files.
251
+ - `swatchkit init`: Scaffolds the layout and token files. If the project is already initialized, prints a status report showing which files differ from their blueprints (auto dry-run).
252
+ - `swatchkit init --force`: Overwrites all init-managed files with the latest blueprints. Custom swatch files and CSS files without blueprint counterparts are never touched.
253
+ - `swatchkit init --dry-run`: Shows what would be created or changed without writing anything.
244
254
 
245
255
  ### Flags
246
256
 
247
- | Flag | Short | Description |
248
- | :--------- | :---- | :---------------------------------------------- |
249
- | `--watch` | `-w` | Watch files and rebuild on change. |
250
- | `--config` | `-c` | Path to config file. |
251
- | `--input` | `-i` | Pattern directory (Default: `swatchkit/`). |
252
- | `--outDir` | `-o` | Output directory (Default: `dist/swatchkit`). |
253
- | `--force` | `-f` | Overwrite layout file during init. |
257
+ | Flag | Short | Description |
258
+ | :---------- | :---- | :-------------------------------------------------------------- |
259
+ | `--watch` | `-w` | Watch files and rebuild on change. |
260
+ | `--config` | `-c` | Path to config file. |
261
+ | `--input` | `-i` | Pattern directory (Default: `swatchkit/`). |
262
+ | `--outDir` | `-o` | Output directory (Default: `dist/swatchkit`). |
263
+ | `--force` | `-f` | Overwrite all init-managed files with latest blueprints. |
264
+ | `--dry-run` | | Show what init would create or change, without writing anything.|
254
265
 
255
266
  ## Configuration
256
267
 
package/build.js CHANGED
@@ -19,6 +19,7 @@ function parseArgs(args) {
19
19
  input: null,
20
20
  outDir: null,
21
21
  force: false,
22
+ dryRun: false,
22
23
  };
23
24
 
24
25
  for (let i = 2; i < args.length; i++) {
@@ -29,6 +30,8 @@ function parseArgs(args) {
29
30
  options.watch = true;
30
31
  } else if (arg === "-f" || arg === "--force") {
31
32
  options.force = true;
33
+ } else if (arg === "--dry-run") {
34
+ options.dryRun = true;
32
35
  } else if (arg === "-c" || arg === "--config") {
33
36
  // Handle case where flag is last arg
34
37
  if (i + 1 < args.length) {
@@ -170,184 +173,227 @@ function resolveSettings(cliOptions, fileConfig) {
170
173
  };
171
174
  }
172
175
 
173
- // --- 4. Init Command Logic ---
174
- function runInit(settings, options) {
175
- console.log("[SwatchKit] Scaffolding project structure...");
176
-
177
- // Ensure swatchkit directory exists
178
- if (!fs.existsSync(settings.swatchkitDir)) {
179
- console.log(`+ Directory: ${settings.swatchkitDir}`);
180
- fs.mkdirSync(settings.swatchkitDir, { recursive: true });
176
+ // --- 4. Init Manifest & Dry Run ---
177
+
178
+ // Builds a list of all files that init manages.
179
+ // Each entry maps a blueprint source to a project destination.
180
+ // Used by both the actual init and the dry-run status report.
181
+ function buildInitManifest(settings) {
182
+ const manifest = [];
183
+ const blueprintsDir = path.join(__dirname, "src/blueprints");
184
+ const templatesDir = path.join(__dirname, "src/templates");
185
+
186
+ // Token blueprint JSON files
187
+ const tokenFiles = [
188
+ "colors.json",
189
+ "text-weights.json",
190
+ "text-leading.json",
191
+ "viewports.json",
192
+ "text-sizes.json",
193
+ "spacing.json",
194
+ "fonts.json",
195
+ ];
196
+ for (const file of tokenFiles) {
197
+ manifest.push({
198
+ src: path.join(blueprintsDir, file),
199
+ dest: path.join(settings.tokensDir, file),
200
+ });
181
201
  }
182
202
 
183
- // Create tokens/ directory at project root (JSON token definitions)
184
- const tokensDir = settings.tokensDir;
185
- if (!fs.existsSync(tokensDir)) {
186
- console.log(`+ Directory: ${tokensDir}`);
187
- fs.mkdirSync(tokensDir, { recursive: true });
203
+ // Template files (swatchkit/tokens/)
204
+ const templateFiles = ["prose.html", "script.js"];
205
+ for (const file of templateFiles) {
206
+ manifest.push({
207
+ src: path.join(templatesDir, file),
208
+ dest: path.join(settings.swatchkitDir, "tokens", file),
209
+ transform: (content) => content.trim(),
210
+ });
188
211
  }
189
212
 
190
- // Create swatchkit/tokens/ directory (HTML/JS visual previews)
191
- const tokensUiDir = path.join(settings.swatchkitDir, "tokens");
192
- if (!fs.existsSync(tokensUiDir)) {
193
- console.log(`+ Directory: ${tokensUiDir}`);
194
- fs.mkdirSync(tokensUiDir, { recursive: true });
195
- }
213
+ // CSS entry point
214
+ manifest.push({
215
+ src: path.join(blueprintsDir, "main.css"),
216
+ dest: settings.mainCssFile,
217
+ });
196
218
 
197
- // Create css/ directory at project root
198
- if (!fs.existsSync(settings.cssDir)) {
199
- console.log(`+ Directory: ${settings.cssDir}`);
200
- fs.mkdirSync(settings.cssDir, { recursive: true });
201
- }
219
+ // SwatchKit UI styles
220
+ manifest.push({
221
+ src: path.join(blueprintsDir, "swatchkit-ui.css"),
222
+ dest: path.join(settings.cssDir, "swatchkit-ui.css"),
223
+ });
202
224
 
203
- // Create css/global directory
204
- const globalCssDir = path.join(settings.cssDir, "global");
205
- if (!fs.existsSync(globalCssDir)) {
206
- console.log(`+ Directory: ${globalCssDir}`);
207
- fs.mkdirSync(globalCssDir, { recursive: true });
225
+ // CSS folder blueprints (global, compositions, utilities)
226
+ const cssFolders = ["global", "compositions", "utilities"];
227
+ for (const folder of cssFolders) {
228
+ const srcDir = path.join(blueprintsDir, folder);
229
+ if (fs.existsSync(srcDir)) {
230
+ const files = fs
231
+ .readdirSync(srcDir)
232
+ .filter((f) => !fs.statSync(path.join(srcDir, f)).isDirectory());
233
+ for (const file of files) {
234
+ manifest.push({
235
+ src: path.join(srcDir, file),
236
+ dest: path.join(settings.cssDir, folder, file),
237
+ });
238
+ }
239
+ }
208
240
  }
209
241
 
210
- // Copy JSON token blueprints to tokens/ (project root)
211
- const copyDefault = (srcFilename, destFilename) => {
212
- const destPath = path.join(tokensDir, destFilename);
213
- if (!fs.existsSync(destPath)) {
214
- const srcPath = path.join(__dirname, "src/blueprints", srcFilename);
215
- const content = fs.readFileSync(srcPath, "utf-8");
216
- fs.writeFileSync(destPath, content);
217
- console.log(`+ Token Blueprint: ${destFilename}`);
218
- }
219
- };
242
+ // Layout files
243
+ manifest.push({
244
+ src: settings.internalLayout,
245
+ dest: settings.projectLayout,
246
+ });
247
+ manifest.push({
248
+ src: settings.internalPreviewLayout,
249
+ dest: settings.projectPreviewLayout,
250
+ });
251
+
252
+ return manifest;
253
+ }
254
+
255
+ // Directories that init ensures exist.
256
+ function getInitDirs(settings) {
257
+ return [
258
+ settings.swatchkitDir,
259
+ settings.tokensDir,
260
+ path.join(settings.swatchkitDir, "tokens"),
261
+ settings.cssDir,
262
+ path.join(settings.cssDir, "global"),
263
+ ];
264
+ }
220
265
 
221
- copyDefault("colors.json", "colors.json");
222
- copyDefault("text-weights.json", "text-weights.json");
223
- copyDefault("text-leading.json", "text-leading.json");
224
- copyDefault("viewports.json", "viewports.json");
225
- copyDefault("text-sizes.json", "text-sizes.json");
226
- copyDefault("spacing.json", "spacing.json");
227
- copyDefault("fonts.json", "fonts.json");
228
-
229
- // Copy HTML/JS template patterns to swatchkit/tokens/ (UI documentation)
230
- // Note: Token display templates (colors, typography, spacing, etc.) are
231
- // generated dynamically at build time from the JSON token files.
232
- const copyTemplate = (srcFilename, destFilename) => {
233
- const destPath = path.join(tokensUiDir, destFilename);
234
- if (!fs.existsSync(destPath)) {
235
- const srcPath = path.join(__dirname, "src/templates", srcFilename);
236
- const content = fs.readFileSync(srcPath, "utf-8");
237
- fs.writeFileSync(destPath, content.trim());
238
- console.log(`+ Pattern Template: ${destFilename}`);
266
+ // Compare init-managed files against their blueprint sources and print a
267
+ // status report showing what would be created, changed, or is up to date.
268
+ function reportInitStatus(settings) {
269
+ const cwd = process.cwd();
270
+ const manifest = buildInitManifest(settings);
271
+ const dirs = getInitDirs(settings);
272
+
273
+ const newDirs = [];
274
+ const created = [];
275
+ const changed = [];
276
+ const upToDate = [];
277
+
278
+ for (const dir of dirs) {
279
+ if (!fs.existsSync(dir)) {
280
+ newDirs.push(path.relative(cwd, dir) + "/");
239
281
  }
240
- };
282
+ }
241
283
 
242
- // Only copy non-token templates (prose is a kitchen sink, not a token display)
243
- copyTemplate("prose.html", "prose.html");
244
-
245
- // Create shared script for tokens UI
246
- const tokensScriptFile = path.join(tokensUiDir, "script.js");
247
- if (!fs.existsSync(tokensScriptFile)) {
248
- const srcPath = path.join(__dirname, "src/templates/script.js");
249
- const content = fs.readFileSync(srcPath, "utf-8");
250
- fs.writeFileSync(tokensScriptFile, content.trim());
251
- console.log(`+ Script: ${path.basename(tokensScriptFile)}`);
252
- }
253
-
254
- // Create main.css entry point
255
- if (!fs.existsSync(settings.mainCssFile)) {
256
- const srcPath = path.join(__dirname, "src/blueprints/main.css");
257
- let content = fs.readFileSync(srcPath, "utf-8");
258
-
259
- // Default: Copy the files
260
- const copyCssBlueprint = (filename) => {
261
- const src = path.join(__dirname, "src/blueprints", filename);
262
- // Ensure destination path (cssDir) exists
263
- if (!fs.existsSync(settings.cssDir)) {
264
- fs.mkdirSync(settings.cssDir, { recursive: true });
265
- }
266
- const dest = path.join(settings.cssDir, filename);
267
- // Only copy if destination doesn't exist
268
- if (fs.existsSync(src) && !fs.existsSync(dest)) {
269
- fs.copyFileSync(src, dest);
270
- console.log(`+ CSS Blueprint: ${filename}`);
284
+ for (const entry of manifest) {
285
+ const relDest = path.relative(cwd, entry.dest);
286
+ if (!fs.existsSync(entry.dest)) {
287
+ created.push(relDest);
288
+ } else {
289
+ let srcContent = fs.readFileSync(entry.src, "utf-8");
290
+ if (entry.transform) srcContent = entry.transform(srcContent);
291
+ const destContent = fs.readFileSync(entry.dest, "utf-8");
292
+ if (srcContent !== destContent) {
293
+ changed.push(relDest);
294
+ } else {
295
+ upToDate.push(relDest);
271
296
  }
272
- };
273
-
274
- // Global blueprints are now copied as a folder below
297
+ }
298
+ }
275
299
 
276
- fs.writeFileSync(settings.mainCssFile, content);
277
- console.log(`+ CSS Blueprint: main.css`);
300
+ if (newDirs.length === 0 && created.length === 0 && changed.length === 0) {
301
+ console.log("[SwatchKit] All init-managed files are up to date.");
302
+ return;
278
303
  }
279
304
 
280
- // Copy Global Styles Folder
281
- const globalSrc = path.join(__dirname, "src/blueprints/global");
282
- const globalDest = path.join(settings.cssDir, "global");
283
- if (fs.existsSync(globalSrc)) {
284
- copyDir(globalSrc, globalDest);
305
+ if (newDirs.length > 0 || created.length > 0) {
306
+ console.log("\n New (will be created):");
307
+ for (const d of newDirs) console.log(` + ${d}`);
308
+ for (const f of created) console.log(` + ${f}`);
285
309
  }
286
310
 
287
- // Copy Compositions
288
- const compositionsSrc = path.join(__dirname, "src/blueprints/compositions");
289
- const compositionsDest = path.join(settings.cssDir, "compositions");
290
- if (fs.existsSync(compositionsSrc)) {
291
- copyDir(compositionsSrc, compositionsDest);
311
+ if (changed.length > 0) {
312
+ console.log("\n Changed (differs from latest blueprint):");
313
+ for (const f of changed) console.log(` ~ ${f}`);
292
314
  }
293
315
 
294
- // Copy Utilities
295
- const utilitiesSrc = path.join(__dirname, "src/blueprints/utilities");
296
- const utilitiesDest = path.join(settings.cssDir, "utilities");
297
- if (fs.existsSync(utilitiesSrc)) {
298
- copyDir(utilitiesSrc, utilitiesDest);
316
+ if (upToDate.length > 0) {
317
+ console.log("\n Up to date:");
318
+ for (const f of upToDate) console.log(` = ${f}`);
299
319
  }
300
320
 
301
- // Copy SwatchKit UI Styles
302
- const uiSrc = path.join(__dirname, "src/blueprints/swatchkit-ui.css");
303
- const uiDest = path.join(settings.cssDir, "swatchkit-ui.css");
304
- if (fs.existsSync(uiSrc) && !fs.existsSync(uiDest)) {
305
- fs.copyFileSync(uiSrc, uiDest);
306
- console.log(`+ CSS Blueprint: swatchkit-ui.css`);
321
+ console.log("");
322
+
323
+ if (changed.length > 0 || created.length > 0 || newDirs.length > 0) {
324
+ console.log(
325
+ " Run 'swatchkit init --force' to update all files to latest blueprints.\n",
326
+ );
307
327
  }
328
+ }
308
329
 
309
- // Generate initial tokens.css
310
- // processTokens now expects the folder where tokens.css should live
311
- // We pass settings.cssDir, but processTokens internally joins 'tokens.css'
312
- // So we need to point it to css/global
313
- const tokensContext = processTokens(settings.tokensDir, path.join(settings.cssDir, "global"));
314
-
315
- if (tokensContext) {
316
- generateTokenUtilities(tokensContext, path.join(settings.cssDir, "utilities"));
330
+ // --- 5. Init Command Logic ---
331
+ function runInit(settings, options) {
332
+ const isInitialized = fs.existsSync(settings.swatchkitDir);
333
+
334
+ // --dry-run: always just report status, change nothing.
335
+ // Already initialized without --force: auto dry-run.
336
+ if (options.dryRun || (isInitialized && !options.force)) {
337
+ if (isInitialized && !options.dryRun) {
338
+ console.log("[SwatchKit] Project already initialized.");
339
+ }
340
+ reportInitStatus(settings);
341
+ return;
342
+ }
343
+
344
+ // Sanity check: internal layout template must exist
345
+ if (!fs.existsSync(settings.internalLayout)) {
346
+ console.error(
347
+ `Error: Internal layout file not found at ${settings.internalLayout}`,
348
+ );
349
+ process.exit(1);
317
350
  }
318
351
 
319
- const targetLayout = settings.projectLayout;
352
+ console.log("[SwatchKit] Scaffolding project structure...");
320
353
 
321
- if (fs.existsSync(targetLayout) && !options.force) {
322
- console.warn(`! Layout already exists: ${targetLayout} (Use --force to overwrite)`);
323
- } else {
324
- if (fs.existsSync(settings.internalLayout)) {
325
- const layoutContent = fs.readFileSync(settings.internalLayout, "utf-8");
326
- fs.writeFileSync(targetLayout, layoutContent);
327
- console.log(`+ Layout: ${path.basename(targetLayout)}`);
328
- } else {
329
- console.error(
330
- `Error: Internal layout file not found at ${settings.internalLayout}`,
331
- );
332
- process.exit(1);
354
+ // Ensure directories exist
355
+ const cwd = process.cwd();
356
+ for (const dir of getInitDirs(settings)) {
357
+ if (!fs.existsSync(dir)) {
358
+ console.log(`+ Directory: ${path.relative(cwd, dir)}/`);
359
+ fs.mkdirSync(dir, { recursive: true });
333
360
  }
334
361
  }
335
362
 
336
- // Copy preview layout template (standalone page for individual swatches)
337
- const targetPreview = settings.projectPreviewLayout;
338
- if (!fs.existsSync(targetPreview)) {
339
- if (fs.existsSync(settings.internalPreviewLayout)) {
340
- const previewContent = fs.readFileSync(
341
- settings.internalPreviewLayout,
342
- "utf-8",
343
- );
344
- fs.writeFileSync(targetPreview, previewContent);
345
- console.log(`+ Layout: ${path.basename(targetPreview)}`);
363
+ // Copy all manifest files
364
+ const manifest = buildInitManifest(settings);
365
+ for (const entry of manifest) {
366
+ const exists = fs.existsSync(entry.dest);
367
+ if (options.force || !exists) {
368
+ // Ensure parent directory exists (for CSS subdirs like compositions/)
369
+ const parentDir = path.dirname(entry.dest);
370
+ if (!fs.existsSync(parentDir)) {
371
+ fs.mkdirSync(parentDir, { recursive: true });
372
+ }
373
+
374
+ let content = fs.readFileSync(entry.src, "utf-8");
375
+ if (entry.transform) content = entry.transform(content);
376
+ fs.writeFileSync(entry.dest, content);
377
+
378
+ const label = path.relative(cwd, entry.dest);
379
+ console.log(`${exists ? "~ Updated" : "+ Created"}: ${label}`);
346
380
  }
347
381
  }
382
+
383
+ // Generate tokens.css and utility tokens.css (always — these are generated files)
384
+ const tokensContext = processTokens(
385
+ settings.tokensDir,
386
+ path.join(settings.cssDir, "global"),
387
+ );
388
+ if (tokensContext) {
389
+ generateTokenUtilities(
390
+ tokensContext,
391
+ path.join(settings.cssDir, "utilities"),
392
+ );
393
+ }
348
394
  }
349
395
 
350
- // --- 5. Build Logic ---
396
+ // --- 6. Build Logic ---
351
397
  function copyDir(src, dest, force = false) {
352
398
  if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
353
399
  const entries = fs.readdirSync(src, { withFileTypes: true });
@@ -729,7 +775,7 @@ function build(settings) {
729
775
  console.log(`Build complete! Generated ${settings.outputFile}`);
730
776
  }
731
777
 
732
- // --- 6. Watch Logic ---
778
+ // --- 7. Watch Logic ---
733
779
  function watch(settings) {
734
780
  const sourcePaths = [
735
781
  settings.swatchkitDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swatchkit",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "A lightweight tool for creating HTML pattern libraries.",
5
5
  "main": "build.js",
6
6
  "bin": {