@pixpilot/scaffoldfy-configs 0.36.4 → 0.37.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @pixpilot/scaffoldfy-configs
2
2
 
3
+ ## 0.37.0
4
+
5
+ ### Minor Changes
6
+
7
+ - add setup-guard scaffold
8
+
9
+ ## 0.36.5
10
+
11
+ ### Patch Changes
12
+
13
+ - update NPM_TOKEN to provenance
14
+
3
15
  ## 0.36.4
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -84,6 +84,16 @@ Usage:
84
84
  npx @pixpilot/scaffoldfy@latest --config https://unpkg.com/@pixpilot/scaffoldfy-configs@latest/security-policy/scaffoldfy.json
85
85
  ```
86
86
 
87
+ ### setup-guard
88
+
89
+ Removes a template's one-time setup gate: strips the guard script from package.json and the git hooks so a generated project stops nagging about an initializer it has already run. Extend this first, and add a `delete` task in your own config to remove the guard's folder once nothing else needs it.
90
+
91
+ Usage:
92
+
93
+ ```sh
94
+ npx @pixpilot/scaffoldfy@latest --config https://unpkg.com/@pixpilot/scaffoldfy-configs@latest/setup-guard/scaffoldfy.json
95
+ ```
96
+
87
97
  ### turbo-workspace-package-generator
88
98
 
89
99
  Pixpilot workspace package generator template for pnpm + Turbo monorepo. Provides project info prompts and config tasks.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pixpilot/scaffoldfy-configs",
3
3
  "type": "module",
4
- "version": "0.36.4",
4
+ "version": "0.37.0",
5
5
  "author": "PixPilot <m.doaie@hotmail.com>",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -30,10 +30,10 @@
30
30
  "@types/node": "^22.18.11",
31
31
  "eslint": "^9.38.0",
32
32
  "@internal/prettier-config": "0.0.1",
33
+ "@internal/eslint-config": "0.3.0",
33
34
  "@internal/tsdown-config": "0.1.0",
34
- "@pixpilot/scaffoldfy": "0.54.0",
35
35
  "@internal/vitest-config": "0.1.0",
36
- "@internal/eslint-config": "0.3.0"
36
+ "@pixpilot/scaffoldfy": "0.54.0"
37
37
  },
38
38
  "prettier": "@internal/prettier-config",
39
39
  "publishConfig": {
@@ -47,4 +47,4 @@ jobs:
47
47
  uses: pixpilot/changesets-autopilot@v1
48
48
  with:
49
49
  GITHUB_TOKEN: ${{ steps.app_token.outputs.token }}
50
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
50
+ provenance: 'true'
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "../node_modules/@pixpilot/scaffoldfy/schema/scaffoldfy.schema.json",
3
+ "name": "setup-guard",
4
+ "description": "Removes a template's one-time setup gate: strips the guard script from package.json and the git hooks so a generated project stops nagging about an initializer it has already run. Extend this first, and add a `delete` task in your own config to remove the guard's folder once nothing else needs it.",
5
+ "tasks": [
6
+ {
7
+ "id": "remove-setup-guard",
8
+ "name": "Remove setup guard",
9
+ "description": "Strip the setup gate from package.json, .husky/* and README.md",
10
+ "type": "exec-file",
11
+ "config": {
12
+ "file": "./scripts/remove-setup-guard.mjs",
13
+ "runtime": "node",
14
+ "args": ["--guard=setup/guard.mjs", "--setup-script=setup"]
15
+ }
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Strips a template's one-time setup gate from the generated project.
4
+ *
5
+ * A template that ships a setup gate has a small guard script (default
6
+ * `setup/guard.mjs`) wired into `postinstall` and `.husky/pre-commit`, so a
7
+ * fresh clone nags on install and refuses to commit until the initializer has
8
+ * been run. This removes those call sites once setup is underway.
9
+ *
10
+ * It deliberately does NOT delete the folder holding the guard: templates often
11
+ * keep other setup assets beside it (handlebars templates, for instance) that
12
+ * later tasks still read. Each template owns that `delete` task and places it
13
+ * after whatever needs those assets.
14
+ *
15
+ * Usage:
16
+ * node remove-setup-guard.mjs [--guard=setup/guard.mjs] [--setup-script=setup]
17
+ *
18
+ * Exits non-zero if a reference survives, so the run stops before any task
19
+ * deletes a script that something still calls.
20
+ */
21
+ import fs from 'node:fs';
22
+ import path from 'node:path';
23
+ import process from 'node:process';
24
+
25
+ /** @param {string} prefix */
26
+ function getArg(prefix) {
27
+ const arg = process.argv.find((value) => value.startsWith(prefix));
28
+ return arg?.slice(prefix.length);
29
+ }
30
+
31
+ const guardRef = getArg('--guard=') || 'setup/guard.mjs';
32
+ const setupScript = getArg('--setup-script=') || 'setup';
33
+ const root = process.cwd();
34
+
35
+ const JSON_INDENT = 2;
36
+ const README_SENTINEL = 'SKIP_SETUP_CHECK';
37
+
38
+ /*
39
+ * Matched loosely on purpose: earlier tasks in a scaffoldfy run re-serialize
40
+ * package.json and can rewrite hook files, so exact-string matching is unsafe.
41
+ */
42
+ const escapedRef = guardRef.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
43
+ const guardSegment = new RegExp(
44
+ `\\s*&&\\s+node\\s+${escapedRef}(?:\\s+\\w+)?|node\\s+${escapedRef}(?:\\s+\\w+)?\\s+&&\\s*`,
45
+ 'u',
46
+ );
47
+
48
+ const done = [];
49
+ const skipped = [];
50
+
51
+ /** @param {string} relativePath */
52
+ function readIfPresent(relativePath) {
53
+ const full = path.join(root, relativePath);
54
+ return fs.existsSync(full) ? { full, text: fs.readFileSync(full, 'utf8') } : null;
55
+ }
56
+
57
+ // 1. package.json -- drop the setup script and unchain the guard from any script.
58
+ {
59
+ const file = readIfPresent('package.json');
60
+ if (file == null) {
61
+ skipped.push('package.json not found');
62
+ } else {
63
+ const pkg = JSON.parse(file.text);
64
+ const scripts = pkg.scripts ?? {};
65
+ let changed = false;
66
+
67
+ if (setupScript in scripts) {
68
+ delete scripts[setupScript];
69
+ changed = true;
70
+ done.push(`removed scripts.${setupScript}`);
71
+ }
72
+
73
+ const guarded = Object.entries(scripts).filter(
74
+ ([, value]) => typeof value === 'string' && value.includes(guardRef),
75
+ );
76
+
77
+ for (const [name, value] of guarded) {
78
+ const stripped = value.replace(guardSegment, '').trim();
79
+ /*
80
+ * Either the guard was one link in a chain (strip it, keep the rest), or
81
+ * the script existed only to call the guard. An empty script value would
82
+ * break `pnpm install`, so drop the key entirely.
83
+ */
84
+ if (stripped === '' || stripped.includes(guardRef)) {
85
+ delete scripts[name];
86
+ done.push(`removed scripts.${name}`);
87
+ } else {
88
+ scripts[name] = stripped;
89
+ done.push(`unchained guard from scripts.${name}`);
90
+ }
91
+ changed = true;
92
+ }
93
+
94
+ if (changed) {
95
+ fs.writeFileSync(file.full, `${JSON.stringify(pkg, null, JSON_INDENT)}\n`);
96
+ } else {
97
+ skipped.push('package.json already clean');
98
+ }
99
+ }
100
+ }
101
+
102
+ // 2. Git hooks -- drop the guard line wherever it was wired in.
103
+ {
104
+ const hooksDirectory = path.join(root, '.husky');
105
+ const hooks = fs.existsSync(hooksDirectory)
106
+ ? fs
107
+ .readdirSync(hooksDirectory, { withFileTypes: true })
108
+ .filter((entry) => entry.isFile())
109
+ .map((entry) => `.husky/${entry.name}`)
110
+ : [];
111
+
112
+ for (const hook of hooks) {
113
+ const file = readIfPresent(hook);
114
+
115
+ if (file != null && file.text.includes(guardRef)) {
116
+ const kept = [];
117
+
118
+ for (const line of file.text.split('\n')) {
119
+ if (line.includes(guardRef)) {
120
+ // Also drop the comment that introduced the check, and its blank line.
121
+ while (kept.length > 0 && kept[kept.length - 1].trimStart().startsWith('#')) {
122
+ kept.pop();
123
+ }
124
+ while (kept.length > 0 && kept[kept.length - 1].trim() === '') {
125
+ kept.pop();
126
+ }
127
+ } else {
128
+ kept.push(line);
129
+ }
130
+ }
131
+
132
+ fs.writeFileSync(file.full, kept.join('\n'));
133
+ done.push(`removed guard from ${hook}`);
134
+ }
135
+ }
136
+ }
137
+
138
+ /*
139
+ * 3. README.md -- usually moot, since templates rewrite the README from their
140
+ * own clean-readme template. Kept as a safety net when that task is disabled.
141
+ */
142
+ {
143
+ const file = readIfPresent('README.md');
144
+ if (file != null && file.text.includes(README_SENTINEL)) {
145
+ const lines = file.text.split('\n');
146
+ let start = lines.findIndex((line) => line.includes(README_SENTINEL));
147
+
148
+ // Walk back to the top of the paragraph, then forward past its blank line.
149
+ while (start > 0 && lines[start - 1].trim() !== '') {
150
+ start -= 1;
151
+ }
152
+ let end = start;
153
+ while (end < lines.length && lines[end].trim() !== '') {
154
+ end += 1;
155
+ }
156
+ while (end < lines.length && lines[end].trim() === '') {
157
+ end += 1;
158
+ }
159
+
160
+ lines.splice(start, end - start);
161
+ fs.writeFileSync(file.full, lines.join('\n'));
162
+ done.push('removed setup note from README.md');
163
+ }
164
+ }
165
+
166
+ for (const message of done) {
167
+ console.warn(` setup-guard: ${message}`);
168
+ }
169
+ for (const message of skipped) {
170
+ console.warn(` setup-guard: skipped -- ${message}`);
171
+ }
172
+
173
+ /*
174
+ * 4. Verify. A template deletes the guard's folder in a later task, so a
175
+ * surviving reference has to stop the run now -- otherwise the project is left
176
+ * calling a script that no longer exists, and every `pnpm install` fails.
177
+ */
178
+ const searched = ['package.json', 'README.md'];
179
+ if (fs.existsSync(path.join(root, '.husky'))) {
180
+ for (const entry of fs.readdirSync(path.join(root, '.husky'), {
181
+ withFileTypes: true,
182
+ })) {
183
+ if (entry.isFile()) {
184
+ searched.push(`.husky/${entry.name}`);
185
+ }
186
+ }
187
+ }
188
+
189
+ const leftovers = searched.filter((relativePath) =>
190
+ readIfPresent(relativePath)?.text.includes(guardRef),
191
+ );
192
+
193
+ if (leftovers.length > 0) {
194
+ console.error(
195
+ ` setup-guard: ERROR -- these still reference ${guardRef}: ${leftovers.join(', ')}`,
196
+ );
197
+ console.error(' setup-guard: remove those lines by hand, then re-run setup.');
198
+ process.exit(1);
199
+ }