create-contractor-site 2.3.0 → 2.3.1

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.
@@ -1,321 +1,321 @@
1
- #!/usr/bin/env node
2
- /**
3
- * create-contractor-site — scaffold a client contractor site from the template.
4
- * Uses pnpm only for install/validate/build. Never invokes npm or npx.
5
- */
6
-
7
- import path from 'node:path';
8
- import {
9
- assertRequiredCopied,
10
- copyTemplate,
11
- resolveTemplateRoot,
12
- } from '../src/copy-template.mjs';
13
- import { collectClientAnswers, buildAnswers } from '../src/prompts.mjs';
14
- import { replaceTargetData } from '../src/replace-data.mjs';
15
- import {
16
- assertGitAvailable,
17
- assertPnpmAvailable,
18
- runGitInit,
19
- runPnpmSetup,
20
- } from '../src/run-command.mjs';
21
- import {
22
- TargetValidationError,
23
- validateTarget,
24
- } from '../src/validate-target.mjs';
25
-
26
- const USAGE = `Usage: create-contractor-site [options] <target-dir>
27
-
28
- Scaffold a client contractor website from the placeholder multipage template.
29
-
30
- Options:
31
- -y, --yes Non-interactive mode using built-in sample answers
32
- -h, --help Show this help
33
-
34
- Environment:
35
- CREATE_CONTRACTOR_SITE_ANSWERS_JSON
36
- JSON object with at least businessName and primaryServices[].
37
- When set, skips prompts (and overrides --yes sample answers).
38
- All paths funnel through buildAnswers (trim + blank defaults).
39
-
40
- CREATE_CONTRACTOR_TEMPLATE_ROOT
41
- Absolute/relative path to a local template checkout (preferred for
42
- local/dev and monorepo use).
43
-
44
- CREATE_CONTRACTOR_TEMPLATE_REPO
45
- Git URL used when the CLI is published and no local template is found.
46
- Default: https://github.com/glacayo/website-multipages.git
47
-
48
- CREATE_CONTRACTOR_TEMPLATE_REF
49
- Git branch/tag/ref to clone for the published fallback. Default: v2.3.0
50
-
51
- Answer defaults (buildAnswers; blank/whitespace → these; --yes omits trust/payment/hours/social/directories/siteType):
52
- freeEstimate Free On-Site Estimate
53
- yearsExperience 10+
54
- license Licensed & Insured
55
- insurance Fully insured with general liability and workers' compensation.
56
- foundedYear "" (optional; key always written; skip/blank → empty string)
57
- paymentMethods Cash, Check, Credit Card, Financing Available (CSV or string[]; blank/[] → defaults, never [])
58
- hours 3-row [{days,time}] Mon–Fri / Sat / Sun (or compact hoursWeekday/hoursSaturday/hoursSunday)
59
- social object or network=url CSV (facebook…x); blank keys omitted
60
- directories [{name,url}] or Name|url CSV; none → ≥1 placeholder + enable_directories false (never [])
61
- siteType multipage (one-page | multipage | seo; aliases like "one page"/"onepage" ok; invalid → multipage)
62
-
63
- Answer precedence (highest first):
64
- 1. CREATE_CONTRACTOR_SITE_ANSWERS_JSON
65
- 2. --yes / -y sample answers
66
- 3. Interactive prompts (default)
67
-
68
- Template source precedence (highest first):
69
- 1. CREATE_CONTRACTOR_TEMPLATE_ROOT
70
- 2. Local monorepo root discovered from this package
71
- 3. Temporary git clone of CREATE_CONTRACTOR_TEMPLATE_REPO @ REF
72
-
73
- Requirements:
74
- - Node.js 22+
75
- - pnpm >= 11.1.2
76
- - git
77
-
78
- The CLI copies the template (denylist excludes node_modules, dist, .git, openspec,
79
- packages/, etc.), applies client values only in src/data/*.json, then runs:
80
- pnpm install
81
- pnpm run validate:data
82
- pnpm run build
83
- git init && git add -A && git commit (only after validate + build succeed)
84
-
85
- If install, validate:data, or build fails, git init/add/commit are intentionally
86
- skipped so a broken scaffold is never committed.
87
-
88
- Package-runner note: you may invoke this binary via pnpm create / compatible runners,
89
- but the CLI itself always uses pnpm internally — never npm install or npx for project setup.
90
- `;
91
-
92
- /**
93
- * Parse CLI args. Supports:
94
- * create-contractor-site <target-dir>
95
- * create-contractor-site --yes <target-dir> (non-interactive defaults; testing)
96
- * CREATE_CONTRACTOR_SITE_ANSWERS_JSON env for scripted answers
97
- */
98
- function parseArgs(argv) {
99
- /** @type {string[]} */
100
- const positionals = [];
101
- let nonInteractive = false;
102
-
103
- for (const arg of argv) {
104
- if (arg === '--help' || arg === '-h') {
105
- return { help: true, positionals, nonInteractive };
106
- }
107
- if (arg === '--yes' || arg === '-y') {
108
- nonInteractive = true;
109
- continue;
110
- }
111
- if (arg.startsWith('-')) {
112
- throw new Error(`Unknown option: ${arg}\n\n${USAGE}`);
113
- }
114
- positionals.push(arg);
115
- }
116
-
117
- return { help: false, positionals, nonInteractive };
118
- }
119
-
120
- /**
121
- * @returns {Promise<import('../src/prompts.mjs').ScaffoldAnswers>}
122
- */
123
- async function resolveAnswers(nonInteractive) {
124
- const fromEnv = process.env.CREATE_CONTRACTOR_SITE_ANSWERS_JSON;
125
- if (fromEnv) {
126
- const parsed = JSON.parse(fromEnv);
127
- if (!parsed.businessName || !Array.isArray(parsed.primaryServices)) {
128
- throw new Error(
129
- 'CREATE_CONTRACTOR_SITE_ANSWERS_JSON must include businessName and primaryServices[]',
130
- );
131
- }
132
- return buildAnswers(parsed);
133
- }
134
-
135
- if (nonInteractive) {
136
- // Omitted trust/payment/hours/social/directories/siteType → buildAnswers defaults (siteType multipage).
137
- return buildAnswers({
138
- businessName: 'Acme Masonry',
139
- legalName: 'Acme Masonry LLC',
140
- tagline: 'Stonework that lasts.',
141
- phone: '(757) 555-0199',
142
- email: 'hello@acmemasonry.example',
143
- street: '100 Scaffold Ave',
144
- city: 'Virginia Beach',
145
- state: 'VA',
146
- zip: '23451',
147
- serviceArea: 'Virginia Beach and Hampton Roads',
148
- primaryServices: ['Masonry', 'Patios', 'Retaining Walls'],
149
- siteUrl: 'https://acmemasonry.example',
150
- });
151
- }
152
-
153
- return collectClientAnswers();
154
- }
155
-
156
- /**
157
- * @param {string} stage
158
- * @param {string} targetDir
159
- * @param {unknown} err
160
- */
161
- function printRecoveryGuidance(stage, targetDir, err) {
162
- const message = err instanceof Error ? err.message : String(err);
163
- console.error(`\nScaffold failed during: ${stage}`);
164
- console.error(message);
165
- console.error(`
166
- Recovery guidance:
167
- Target path: ${targetDir || '(not created yet)'}
168
-
169
- 1. Inspect the target directory. Partial files may remain after a failed
170
- copy, JSON replace, install, build, or git step.
171
- 2. Fix the underlying issue (pnpm/git install, network, disk space, data).
172
- 3. Remove the incomplete target directory if you want a clean retry:
173
- rm -rf "${targetDir || '<target-dir>'}" # or Delete on Windows
174
- 4. Re-run create-contractor-site with the same arguments.
175
-
176
- Notes:
177
- - The source template is never modified (writes are target-only).
178
- - Install/build always use pnpm — never npm install / npx.
179
- - git init / git add / git commit run ONLY after validate:data and build succeed.
180
- If this failure was during install, validate:data, or build, those git steps
181
- were intentionally skipped (a broken scaffold is never committed).
182
- - If a temp template clone was used, it is cleaned up automatically when possible.
183
- `);
184
- }
185
-
186
- async function main() {
187
- const { help, positionals, nonInteractive } = parseArgs(
188
- process.argv.slice(2),
189
- );
190
-
191
- if (help) {
192
- console.log(USAGE);
193
- process.exit(0);
194
- }
195
-
196
- const targetArg = positionals[0];
197
- if (!targetArg) {
198
- console.error('Missing target directory.\n');
199
- console.error(USAGE);
200
- process.exit(1);
201
- }
202
-
203
- console.log('create-contractor-site — Contractor multipage template scaffold');
204
- console.log('pnpm-only installs. Placeholder template → client project.\n');
205
-
206
- // Preconditions before any filesystem writes.
207
- try {
208
- assertPnpmAvailable();
209
- assertGitAvailable();
210
- } catch (err) {
211
- console.error(err instanceof Error ? err.message : err);
212
- process.exit(1);
213
- }
214
-
215
- /** @type {(() => void) | null} */
216
- let cleanupTemplate = null;
217
- /** @type {string} */
218
- let targetDir = '';
219
- /** @type {string} */
220
- let stage = 'init';
221
-
222
- try {
223
- stage = 'resolve-template';
224
- const template = resolveTemplateRoot();
225
- cleanupTemplate = template.cleanup;
226
- const templateRoot = template.root;
227
-
228
- stage = 'validate-target';
229
- try {
230
- targetDir = validateTarget(targetArg, { templateRoot });
231
- } catch (err) {
232
- if (err instanceof TargetValidationError) {
233
- console.error(err.message);
234
- process.exitCode = 1;
235
- return;
236
- }
237
- throw err;
238
- }
239
-
240
- console.log(
241
- `Template root: ${templateRoot} (${template.source}${template.source === 'clone' ? ', temp' : ''})`,
242
- );
243
- console.log(`Target: ${targetDir}`);
244
-
245
- stage = 'collect-answers';
246
- const answers = await resolveAnswers(nonInteractive);
247
-
248
- stage = 'copy-template';
249
- console.log('\n→ Copying template files…');
250
- const { copiedFiles, skipped } = copyTemplate(templateRoot, targetDir);
251
- console.log(
252
- ` Copied ${copiedFiles} files (skipped ${skipped.length} denied paths).`,
253
- );
254
-
255
- stage = 'assert-copy';
256
- const missing = assertRequiredCopied(targetDir);
257
- if (missing.length > 0) {
258
- throw new Error(
259
- `Copy incomplete. Missing required paths:\n${missing.map((m) => ` - ${m}`).join('\n')}`,
260
- );
261
- }
262
-
263
- stage = 'replace-data';
264
- console.log('→ Replacing JSON values in src/data (shape preserved)…');
265
- replaceTargetData(targetDir, answers);
266
-
267
- const skipSetupForTests = process.env.CREATE_CONTRACTOR_SITE_SKIP_SETUP === '1';
268
- if (skipSetupForTests && process.env.NODE_ENV !== 'test') {
269
- throw new Error('CREATE_CONTRACTOR_SITE_SKIP_SETUP is test-only and requires NODE_ENV=test.');
270
- }
271
-
272
- if (skipSetupForTests) {
273
- console.log(`
274
- ✓ Data replace complete (setup skipped)
275
-
276
- Path: ${targetDir}
277
- CREATE_CONTRACTOR_SITE_SKIP_SETUP=1 — pnpm install/validate/build and git init were skipped.
278
- `);
279
- return;
280
- }
281
-
282
- stage = 'pnpm-setup';
283
- runPnpmSetup(targetDir);
284
-
285
- stage = 'git-init';
286
- runGitInit(targetDir);
287
-
288
- console.log(`
289
- ✓ Scaffold complete
290
-
291
- Path: ${targetDir}
292
-
293
- Next steps:
294
- cd ${path.relative(process.cwd(), targetDir) || '.'}
295
- pnpm run dev
296
-
297
- Remember:
298
- - Customize remaining copy in src/data/*.json
299
- - Keep pnpm only (never npm install / npx for this project)
300
- - Run pnpm run validate:data and pnpm run build before deploy
301
- `);
302
- } catch (err) {
303
- printRecoveryGuidance(stage, targetDir, err);
304
- process.exitCode = 1;
305
- } finally {
306
- if (cleanupTemplate) {
307
- try {
308
- cleanupTemplate();
309
- } catch {
310
- console.error(
311
- 'Warning: could not remove temporary template clone. You may delete it manually from your system temp directory.',
312
- );
313
- }
314
- }
315
- }
316
- }
317
-
318
- main().catch((err) => {
319
- console.error(err instanceof Error ? err.message : err);
320
- process.exit(1);
321
- });
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-contractor-site — scaffold a client contractor site from the template.
4
+ * Uses pnpm only for install/validate/build. Never invokes npm or npx.
5
+ */
6
+
7
+ import path from 'node:path';
8
+ import {
9
+ assertRequiredCopied,
10
+ copyTemplate,
11
+ resolveTemplateRoot,
12
+ } from '../src/copy-template.mjs';
13
+ import { collectClientAnswers, buildAnswers } from '../src/prompts.mjs';
14
+ import { replaceTargetData } from '../src/replace-data.mjs';
15
+ import {
16
+ assertGitAvailable,
17
+ assertPnpmAvailable,
18
+ runGitInit,
19
+ runPnpmSetup,
20
+ } from '../src/run-command.mjs';
21
+ import {
22
+ TargetValidationError,
23
+ validateTarget,
24
+ } from '../src/validate-target.mjs';
25
+
26
+ const USAGE = `Usage: create-contractor-site [options] <target-dir>
27
+
28
+ Scaffold a client contractor website from the placeholder multipage template.
29
+
30
+ Options:
31
+ -y, --yes Non-interactive mode using built-in sample answers
32
+ -h, --help Show this help
33
+
34
+ Environment:
35
+ CREATE_CONTRACTOR_SITE_ANSWERS_JSON
36
+ JSON object with at least businessName and primaryServices[].
37
+ When set, skips prompts (and overrides --yes sample answers).
38
+ All paths funnel through buildAnswers (trim + blank defaults).
39
+
40
+ CREATE_CONTRACTOR_TEMPLATE_ROOT
41
+ Absolute/relative path to a local template checkout (preferred for
42
+ local/dev and monorepo use).
43
+
44
+ CREATE_CONTRACTOR_TEMPLATE_REPO
45
+ Git URL used when the CLI is published and no local template is found.
46
+ Default: https://github.com/glacayo/website-multipages.git
47
+
48
+ CREATE_CONTRACTOR_TEMPLATE_REF
49
+ Git branch/tag/ref to clone for the published fallback. Default: v2.3.1
50
+
51
+ Answer defaults (buildAnswers; blank/whitespace → these; --yes omits trust/payment/hours/social/directories/siteType):
52
+ freeEstimate Free On-Site Estimate
53
+ yearsExperience 10+
54
+ license Licensed & Insured
55
+ insurance Fully insured with general liability and workers' compensation.
56
+ foundedYear "" (optional; key always written; skip/blank → empty string)
57
+ paymentMethods Cash, Check, Credit Card, Financing Available (CSV or string[]; blank/[] → defaults, never [])
58
+ hours 3-row [{days,time}] Mon–Fri / Sat / Sun (or compact hoursWeekday/hoursSaturday/hoursSunday)
59
+ social object or network=url CSV (facebook…x); blank keys omitted
60
+ directories [{name,url}] or Name|url CSV; none → ≥1 placeholder + enable_directories false (never [])
61
+ siteType multipage (one-page | multipage | seo; aliases like "one page"/"onepage" ok; invalid → multipage)
62
+
63
+ Answer precedence (highest first):
64
+ 1. CREATE_CONTRACTOR_SITE_ANSWERS_JSON
65
+ 2. --yes / -y sample answers
66
+ 3. Interactive prompts (default)
67
+
68
+ Template source precedence (highest first):
69
+ 1. CREATE_CONTRACTOR_TEMPLATE_ROOT
70
+ 2. Local monorepo root discovered from this package
71
+ 3. Temporary git clone of CREATE_CONTRACTOR_TEMPLATE_REPO @ REF
72
+
73
+ Requirements:
74
+ - Node.js 22+
75
+ - pnpm >= 11.1.2
76
+ - git
77
+
78
+ The CLI copies the template (denylist excludes node_modules, dist, .git, openspec,
79
+ packages/, etc.), applies client values only in src/data/*.json, then runs:
80
+ pnpm install
81
+ pnpm run validate:data
82
+ pnpm run build
83
+ git init && git add -A && git commit (only after validate + build succeed)
84
+
85
+ If install, validate:data, or build fails, git init/add/commit are intentionally
86
+ skipped so a broken scaffold is never committed.
87
+
88
+ Package-runner note: you may invoke this binary via pnpm create / compatible runners,
89
+ but the CLI itself always uses pnpm internally — never npm install or npx for project setup.
90
+ `;
91
+
92
+ /**
93
+ * Parse CLI args. Supports:
94
+ * create-contractor-site <target-dir>
95
+ * create-contractor-site --yes <target-dir> (non-interactive defaults; testing)
96
+ * CREATE_CONTRACTOR_SITE_ANSWERS_JSON env for scripted answers
97
+ */
98
+ function parseArgs(argv) {
99
+ /** @type {string[]} */
100
+ const positionals = [];
101
+ let nonInteractive = false;
102
+
103
+ for (const arg of argv) {
104
+ if (arg === '--help' || arg === '-h') {
105
+ return { help: true, positionals, nonInteractive };
106
+ }
107
+ if (arg === '--yes' || arg === '-y') {
108
+ nonInteractive = true;
109
+ continue;
110
+ }
111
+ if (arg.startsWith('-')) {
112
+ throw new Error(`Unknown option: ${arg}\n\n${USAGE}`);
113
+ }
114
+ positionals.push(arg);
115
+ }
116
+
117
+ return { help: false, positionals, nonInteractive };
118
+ }
119
+
120
+ /**
121
+ * @returns {Promise<import('../src/prompts.mjs').ScaffoldAnswers>}
122
+ */
123
+ async function resolveAnswers(nonInteractive) {
124
+ const fromEnv = process.env.CREATE_CONTRACTOR_SITE_ANSWERS_JSON;
125
+ if (fromEnv) {
126
+ const parsed = JSON.parse(fromEnv);
127
+ if (!parsed.businessName || !Array.isArray(parsed.primaryServices)) {
128
+ throw new Error(
129
+ 'CREATE_CONTRACTOR_SITE_ANSWERS_JSON must include businessName and primaryServices[]',
130
+ );
131
+ }
132
+ return buildAnswers(parsed);
133
+ }
134
+
135
+ if (nonInteractive) {
136
+ // Omitted trust/payment/hours/social/directories/siteType → buildAnswers defaults (siteType multipage).
137
+ return buildAnswers({
138
+ businessName: 'Acme Masonry',
139
+ legalName: 'Acme Masonry LLC',
140
+ tagline: 'Stonework that lasts.',
141
+ phone: '(757) 555-0199',
142
+ email: 'hello@acmemasonry.example',
143
+ street: '100 Scaffold Ave',
144
+ city: 'Virginia Beach',
145
+ state: 'VA',
146
+ zip: '23451',
147
+ serviceArea: 'Virginia Beach and Hampton Roads',
148
+ primaryServices: ['Masonry', 'Patios', 'Retaining Walls'],
149
+ siteUrl: 'https://acmemasonry.example',
150
+ });
151
+ }
152
+
153
+ return collectClientAnswers();
154
+ }
155
+
156
+ /**
157
+ * @param {string} stage
158
+ * @param {string} targetDir
159
+ * @param {unknown} err
160
+ */
161
+ function printRecoveryGuidance(stage, targetDir, err) {
162
+ const message = err instanceof Error ? err.message : String(err);
163
+ console.error(`\nScaffold failed during: ${stage}`);
164
+ console.error(message);
165
+ console.error(`
166
+ Recovery guidance:
167
+ Target path: ${targetDir || '(not created yet)'}
168
+
169
+ 1. Inspect the target directory. Partial files may remain after a failed
170
+ copy, JSON replace, install, build, or git step.
171
+ 2. Fix the underlying issue (pnpm/git install, network, disk space, data).
172
+ 3. Remove the incomplete target directory if you want a clean retry:
173
+ rm -rf "${targetDir || '<target-dir>'}" # or Delete on Windows
174
+ 4. Re-run create-contractor-site with the same arguments.
175
+
176
+ Notes:
177
+ - The source template is never modified (writes are target-only).
178
+ - Install/build always use pnpm — never npm install / npx.
179
+ - git init / git add / git commit run ONLY after validate:data and build succeed.
180
+ If this failure was during install, validate:data, or build, those git steps
181
+ were intentionally skipped (a broken scaffold is never committed).
182
+ - If a temp template clone was used, it is cleaned up automatically when possible.
183
+ `);
184
+ }
185
+
186
+ async function main() {
187
+ const { help, positionals, nonInteractive } = parseArgs(
188
+ process.argv.slice(2),
189
+ );
190
+
191
+ if (help) {
192
+ console.log(USAGE);
193
+ process.exit(0);
194
+ }
195
+
196
+ const targetArg = positionals[0];
197
+ if (!targetArg) {
198
+ console.error('Missing target directory.\n');
199
+ console.error(USAGE);
200
+ process.exit(1);
201
+ }
202
+
203
+ console.log('create-contractor-site — Contractor multipage template scaffold');
204
+ console.log('pnpm-only installs. Placeholder template → client project.\n');
205
+
206
+ // Preconditions before any filesystem writes.
207
+ try {
208
+ assertPnpmAvailable();
209
+ assertGitAvailable();
210
+ } catch (err) {
211
+ console.error(err instanceof Error ? err.message : err);
212
+ process.exit(1);
213
+ }
214
+
215
+ /** @type {(() => void) | null} */
216
+ let cleanupTemplate = null;
217
+ /** @type {string} */
218
+ let targetDir = '';
219
+ /** @type {string} */
220
+ let stage = 'init';
221
+
222
+ try {
223
+ stage = 'resolve-template';
224
+ const template = resolveTemplateRoot();
225
+ cleanupTemplate = template.cleanup;
226
+ const templateRoot = template.root;
227
+
228
+ stage = 'validate-target';
229
+ try {
230
+ targetDir = validateTarget(targetArg, { templateRoot });
231
+ } catch (err) {
232
+ if (err instanceof TargetValidationError) {
233
+ console.error(err.message);
234
+ process.exitCode = 1;
235
+ return;
236
+ }
237
+ throw err;
238
+ }
239
+
240
+ console.log(
241
+ `Template root: ${templateRoot} (${template.source}${template.source === 'clone' ? ', temp' : ''})`,
242
+ );
243
+ console.log(`Target: ${targetDir}`);
244
+
245
+ stage = 'collect-answers';
246
+ const answers = await resolveAnswers(nonInteractive);
247
+
248
+ stage = 'copy-template';
249
+ console.log('\n→ Copying template files…');
250
+ const { copiedFiles, skipped } = copyTemplate(templateRoot, targetDir);
251
+ console.log(
252
+ ` Copied ${copiedFiles} files (skipped ${skipped.length} denied paths).`,
253
+ );
254
+
255
+ stage = 'assert-copy';
256
+ const missing = assertRequiredCopied(targetDir);
257
+ if (missing.length > 0) {
258
+ throw new Error(
259
+ `Copy incomplete. Missing required paths:\n${missing.map((m) => ` - ${m}`).join('\n')}`,
260
+ );
261
+ }
262
+
263
+ stage = 'replace-data';
264
+ console.log('→ Replacing JSON values in src/data (shape preserved)…');
265
+ replaceTargetData(targetDir, answers);
266
+
267
+ const skipSetupForTests = process.env.CREATE_CONTRACTOR_SITE_SKIP_SETUP === '1';
268
+ if (skipSetupForTests && process.env.NODE_ENV !== 'test') {
269
+ throw new Error('CREATE_CONTRACTOR_SITE_SKIP_SETUP is test-only and requires NODE_ENV=test.');
270
+ }
271
+
272
+ if (skipSetupForTests) {
273
+ console.log(`
274
+ ✓ Data replace complete (setup skipped)
275
+
276
+ Path: ${targetDir}
277
+ CREATE_CONTRACTOR_SITE_SKIP_SETUP=1 — pnpm install/validate/build and git init were skipped.
278
+ `);
279
+ return;
280
+ }
281
+
282
+ stage = 'pnpm-setup';
283
+ runPnpmSetup(targetDir);
284
+
285
+ stage = 'git-init';
286
+ runGitInit(targetDir);
287
+
288
+ console.log(`
289
+ ✓ Scaffold complete
290
+
291
+ Path: ${targetDir}
292
+
293
+ Next steps:
294
+ cd ${path.relative(process.cwd(), targetDir) || '.'}
295
+ pnpm run dev
296
+
297
+ Remember:
298
+ - Customize remaining copy in src/data/*.json
299
+ - Keep pnpm only (never npm install / npx for this project)
300
+ - Run pnpm run validate:data and pnpm run build before deploy
301
+ `);
302
+ } catch (err) {
303
+ printRecoveryGuidance(stage, targetDir, err);
304
+ process.exitCode = 1;
305
+ } finally {
306
+ if (cleanupTemplate) {
307
+ try {
308
+ cleanupTemplate();
309
+ } catch {
310
+ console.error(
311
+ 'Warning: could not remove temporary template clone. You may delete it manually from your system temp directory.',
312
+ );
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+ main().catch((err) => {
319
+ console.error(err instanceof Error ? err.message : err);
320
+ process.exit(1);
321
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-contractor-site",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Scaffold a client contractor website from the placeholder multipage template",
5
5
  "type": "module",
6
6
  "bin": {