create-codebase-snapshot 4.0.6 → 4.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/README.md +23 -2
- package/index.mjs +132 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,8 +8,10 @@ npm create codebase-snapshot
|
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
It detects the project type, asks a handful of questions with sensible defaults,
|
|
11
|
-
and writes
|
|
12
|
-
|
|
11
|
+
and writes the config file this project can actually load — `snapshot.config.ts`
|
|
12
|
+
for an ES-module project on Node ≥ 22.6, `snapshot.config.mjs` everywhere else —
|
|
13
|
+
listing **every setting at its default**, so the config doubles as the
|
|
14
|
+
documentation.
|
|
13
15
|
|
|
14
16
|
```bash
|
|
15
17
|
npm create codebase-snapshot -- --preset react-vite --yes
|
|
@@ -22,5 +24,24 @@ npm create codebase-snapshot -- --preset react-vite --yes
|
|
|
22
24
|
| `--minimal` | Write only the settings that differ from defaults |
|
|
23
25
|
| `--force` | Overwrite an existing config |
|
|
24
26
|
|
|
27
|
+
## What it writes
|
|
28
|
+
|
|
29
|
+
| Path | Notes |
|
|
30
|
+
| :--- | :--- |
|
|
31
|
+
| `snapshot.config.ts` / `snapshot.config.mjs` | Every setting at its default. `.ts` needs an ES-module `package.json` and Node ≥ 22.6; `.mjs` is used otherwise, because a `.ts` config is loaded with the enclosing package's module type. |
|
|
32
|
+
| `package.json` → `scripts.snapshot`, `scripts.snapshot:validate` | Skipped when there is no `package.json` to hold them. |
|
|
33
|
+
| `package.json` → `devDependencies` | `@ankurhalder/codebase-snapshot` at the range this scaffolder shipped with. |
|
|
34
|
+
| `.gitignore` | `snapshot/`, `snapshot.tmp/`. |
|
|
35
|
+
|
|
36
|
+
The generated scripts run `npx @ankurhalder/codebase-snapshot …`, and `npx` uses the
|
|
37
|
+
local `devDependencies` copy when there is one — so the version is pinned by your
|
|
38
|
+
lockfile instead of drifting to `@latest`. Until you run `npm install`, `npx` would
|
|
39
|
+
fall back to downloading the newest release, which is why the printed next step is
|
|
40
|
+
`npm install && npm run snapshot`. Nothing is installed, and no network is used,
|
|
41
|
+
while scaffolding.
|
|
42
|
+
|
|
43
|
+
Without a `package.json` the scripts cannot be added, and the printed next step is
|
|
44
|
+
`npx @ankurhalder/codebase-snapshot generate` instead.
|
|
45
|
+
|
|
25
46
|
This package is a shim: the wizard itself lives in the main package, so
|
|
26
47
|
`npx @ankurhalder/codebase-snapshot init` does exactly the same thing.
|
package/index.mjs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import * as path from "node:path";
|
|
2
5
|
import { PromptCancelled, runInit } from "@ankurhalder/codebase-snapshot";
|
|
3
6
|
|
|
4
7
|
/**
|
|
@@ -10,6 +13,126 @@ import { PromptCancelled, runInit } from "@ankurhalder/codebase-snapshot";
|
|
|
10
13
|
* `npm create <x>` is the convention developers reach for first.
|
|
11
14
|
*/
|
|
12
15
|
|
|
16
|
+
const PACKAGE_NAME = "@ankurhalder/codebase-snapshot";
|
|
17
|
+
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
/** The range this shim was published against, read from its own manifest. */
|
|
21
|
+
function dependencyRange() {
|
|
22
|
+
try {
|
|
23
|
+
const manifest = require("./package.json");
|
|
24
|
+
const range = manifest?.dependencies?.[PACKAGE_NAME];
|
|
25
|
+
return typeof range === "string" ? range : null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Keeps the file's own indentation and trailing newline when rewriting JSON. */
|
|
32
|
+
function serializePackageJson(raw, value) {
|
|
33
|
+
const indent = /^[{[]\r?\n([ \t]+)/.exec(raw)?.[1];
|
|
34
|
+
const spacing = raw.includes("\n") ? (indent ?? " ") : undefined;
|
|
35
|
+
const body = JSON.stringify(value, null, spacing);
|
|
36
|
+
return `${body}${raw.endsWith("\n") ? "\n" : ""}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Records the runtime dependency the generated scripts need.
|
|
41
|
+
*
|
|
42
|
+
* The wizard writes `npx @ankurhalder/codebase-snapshot generate`, which resolves
|
|
43
|
+
* the *latest* release when nothing is installed locally: the first run silently
|
|
44
|
+
* downloaded @latest (observed pulling an older minor), failed behind a proxy and
|
|
45
|
+
* could cross a major version. Naming the package in `devDependencies` makes
|
|
46
|
+
* `npx` prefer that local copy, so the scripts become version-locked once the
|
|
47
|
+
* project is installed — without this shim ever touching the network itself.
|
|
48
|
+
*/
|
|
49
|
+
function recordDependency(written) {
|
|
50
|
+
if (!written) return { added: false };
|
|
51
|
+
|
|
52
|
+
const range = dependencyRange();
|
|
53
|
+
if (!range) {
|
|
54
|
+
return {
|
|
55
|
+
added: false,
|
|
56
|
+
note: `Could not read ${PACKAGE_NAME}'s version from this package, so it was not added to devDependencies.`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const pkgPath = path.join(process.cwd(), "package.json");
|
|
61
|
+
if (!fs.existsSync(pkgPath)) return { added: false };
|
|
62
|
+
|
|
63
|
+
let raw;
|
|
64
|
+
try {
|
|
65
|
+
raw = fs.readFileSync(pkgPath, "utf8");
|
|
66
|
+
} catch {
|
|
67
|
+
return {
|
|
68
|
+
added: false,
|
|
69
|
+
note: `package.json could not be read, so ${PACKAGE_NAME} was not added to devDependencies.`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let pkg;
|
|
74
|
+
try {
|
|
75
|
+
pkg = JSON.parse(raw);
|
|
76
|
+
} catch {
|
|
77
|
+
return {
|
|
78
|
+
added: false,
|
|
79
|
+
note: `package.json could not be parsed, so ${PACKAGE_NAME} was not added to devDependencies.`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const declared = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"].some(
|
|
84
|
+
(field) => pkg?.[field] && typeof pkg[field] === "object" && PACKAGE_NAME in pkg[field]
|
|
85
|
+
);
|
|
86
|
+
if (declared) return { added: false, range };
|
|
87
|
+
|
|
88
|
+
pkg.devDependencies = { ...(pkg.devDependencies ?? {}), [PACKAGE_NAME]: range };
|
|
89
|
+
try {
|
|
90
|
+
fs.writeFileSync(pkgPath, serializePackageJson(raw, pkg));
|
|
91
|
+
} catch {
|
|
92
|
+
return {
|
|
93
|
+
added: false,
|
|
94
|
+
note: `package.json could not be written, so ${PACKAGE_NAME} was not added to devDependencies.`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { added: true, range };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** True when the package's own binary is already installed in the project. */
|
|
101
|
+
function hasLocalBinary() {
|
|
102
|
+
const names =
|
|
103
|
+
process.platform === "win32"
|
|
104
|
+
? ["codebase-snapshot.cmd", "codebase-snapshot"]
|
|
105
|
+
: ["codebase-snapshot"];
|
|
106
|
+
return names.some((name) =>
|
|
107
|
+
fs.existsSync(path.join(process.cwd(), "node_modules", ".bin", name))
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** True when the project's package.json already carries the `snapshot` script. */
|
|
112
|
+
function hasSnapshotScript() {
|
|
113
|
+
try {
|
|
114
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8"));
|
|
115
|
+
return typeof pkg?.scripts?.snapshot === "string";
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The command the developer should run next.
|
|
123
|
+
*
|
|
124
|
+
* `npm run snapshot` is only meaningful when the project has that script, which
|
|
125
|
+
* needs a `package.json`: the old text promised it unconditionally, so the
|
|
126
|
+
* documented next step failed with ENOENT in exactly the case that needs help
|
|
127
|
+
* most. A pre-existing script counts even when this run added nothing, hence the
|
|
128
|
+
* check is against `package.json` rather than against what the wizard wrote. The
|
|
129
|
+
* remaining forms are executable as printed.
|
|
130
|
+
*/
|
|
131
|
+
function nextStep() {
|
|
132
|
+
if (!hasSnapshotScript()) return `npx ${PACKAGE_NAME} generate`;
|
|
133
|
+
return hasLocalBinary() ? "npm run snapshot" : "npm install && npm run snapshot";
|
|
134
|
+
}
|
|
135
|
+
|
|
13
136
|
const argv = process.argv.slice(2);
|
|
14
137
|
|
|
15
138
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
@@ -60,8 +183,16 @@ try {
|
|
|
60
183
|
if (result.gitignoreEntries.length > 0) {
|
|
61
184
|
process.stderr.write(` Added to .gitignore: ${result.gitignoreEntries.join(", ")}\n`);
|
|
62
185
|
}
|
|
186
|
+
|
|
187
|
+
const dependency = recordDependency(result.written);
|
|
188
|
+
if (dependency.added) {
|
|
189
|
+
process.stderr.write(` Added devDependency: ${PACKAGE_NAME} ${dependency.range}\n`);
|
|
190
|
+
} else if (dependency.note) {
|
|
191
|
+
process.stderr.write(` ⚠ ${dependency.note}\n`);
|
|
192
|
+
}
|
|
193
|
+
|
|
63
194
|
for (const note of result.notes) process.stderr.write(` ⚠ ${note}\n`);
|
|
64
|
-
process.stderr.write(
|
|
195
|
+
process.stderr.write(`\n Next: ${nextStep()}\n\n`);
|
|
65
196
|
} catch (err) {
|
|
66
197
|
if (err instanceof PromptCancelled) {
|
|
67
198
|
process.stderr.write("\n❌ Cancelled.\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-codebase-snapshot",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Set up @ankurhalder/codebase-snapshot in a repository",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ankur Halder <ankur.halder12345@gmail.com>",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"README.md"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@ankurhalder/codebase-snapshot": "^4.0
|
|
25
|
+
"@ankurhalder/codebase-snapshot": "^4.1.0"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=18.0.0"
|