@projectdochelp/s3te 3.0.0 → 3.1.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.
package/README.md CHANGED
@@ -136,9 +136,25 @@ cd mywebsite
136
136
  </details>
137
137
 
138
138
  <details>
139
- <summary>2. Scaffold the project once</summary>
139
+ <summary>2. Install S3TE locally in the project</summary>
140
140
 
141
- If `@projectdochelp/s3te` is already published on npm, scaffold the project once with a temporary `npx` run:
141
+ ```bash
142
+ npm install --save-dev @projectdochelp/s3te
143
+ ```
144
+ </details>
145
+
146
+ <details>
147
+ <summary>3. Scaffold the project</summary>
148
+
149
+ With the local package installed, initialize the project like this:
150
+
151
+ ```bash
152
+ npx s3te init --project-name mywebsite --base-url example.com
153
+ ```
154
+
155
+ If `npm install` already created a minimal `package.json`, `s3te init` extends it with the missing S3TE defaults and scripts instead of failing.
156
+
157
+ If you want a one-shot scaffold without installing first, and `@projectdochelp/s3te` is already published on npm, this also works:
142
158
 
143
159
  ```bash
144
160
  npx --package @projectdochelp/s3te s3te init --project-name mywebsite --base-url example.com
@@ -155,7 +171,7 @@ node packages/cli/bin/s3te.mjs init --dir ./mywebsite --project-name mywebsite -
155
171
  </details>
156
172
 
157
173
  <details>
158
- <summary>3. What the scaffold creates</summary>
174
+ <summary>4. What the scaffold creates</summary>
159
175
 
160
176
  The default scaffold creates:
161
177
 
@@ -181,20 +197,6 @@ mywebsite/
181
197
 
182
198
  </details>
183
199
 
184
- <details>
185
- <summary>4. Install S3TE locally in the project</summary>
186
-
187
- ```bash
188
- npm install --save-dev @projectdochelp/s3te
189
- npx s3te --help
190
- ```
191
-
192
- This is the recommended everyday setup. From this point on, you run the project-local version with `npx s3te ...`.
193
-
194
- If you want to use the built-in test helpers in your own project tests, import them from the same package via `@projectdochelp/s3te/testkit`.
195
-
196
- </details>
197
-
198
200
  <details>
199
201
  <summary>5. Fill in the real AWS values in <code>s3te.config.json</code></summary>
200
202
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectdochelp/s3te",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "description": "CLI, render core, AWS adapter, and testkit for S3TemplateEngine projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -125,6 +125,10 @@ async function fileExists(targetPath) {
125
125
  }
126
126
  }
127
127
 
128
+ function isPlainObject(value) {
129
+ return value !== null && typeof value === "object" && !Array.isArray(value);
130
+ }
131
+
128
132
  async function writeProjectFile(targetPath, body, force = false) {
129
133
  if (!force && await fileExists(targetPath)) {
130
134
  throw new Error(`Refusing to overwrite existing file: ${targetPath}`);
@@ -132,6 +136,57 @@ async function writeProjectFile(targetPath, body, force = false) {
132
136
  await writeTextFile(targetPath, body);
133
137
  }
134
138
 
139
+ function mergeProjectPackageJson(existingPackageJson, projectPackageJson) {
140
+ if (!isPlainObject(existingPackageJson)) {
141
+ throw new Error("Existing package.json must contain a JSON object.");
142
+ }
143
+
144
+ if (existingPackageJson.scripts !== undefined && !isPlainObject(existingPackageJson.scripts)) {
145
+ throw new Error("Existing package.json must use an object for scripts.");
146
+ }
147
+
148
+ const mergedPackageJson = { ...existingPackageJson };
149
+ for (const [key, value] of Object.entries(projectPackageJson)) {
150
+ if (key === "scripts") {
151
+ continue;
152
+ }
153
+
154
+ if (mergedPackageJson[key] === undefined) {
155
+ mergedPackageJson[key] = value;
156
+ }
157
+ }
158
+
159
+ const mergedScripts = { ...(mergedPackageJson.scripts ?? {}) };
160
+ for (const [name, command] of Object.entries(projectPackageJson.scripts ?? {})) {
161
+ if (mergedScripts[name] === undefined) {
162
+ mergedScripts[name] = command;
163
+ }
164
+ }
165
+
166
+ if (Object.keys(mergedScripts).length > 0) {
167
+ mergedPackageJson.scripts = mergedScripts;
168
+ }
169
+
170
+ return mergedPackageJson;
171
+ }
172
+
173
+ async function writeProjectPackageJson(targetPath, projectPackageJson, force = false) {
174
+ if (force || !await fileExists(targetPath)) {
175
+ await writeTextFile(targetPath, JSON.stringify(projectPackageJson, null, 2) + "\n");
176
+ return;
177
+ }
178
+
179
+ let existingPackageJson;
180
+ try {
181
+ existingPackageJson = JSON.parse(await fs.readFile(targetPath, "utf8"));
182
+ } catch (error) {
183
+ throw new Error(`Existing package.json is not valid JSON: ${targetPath}`, { cause: error });
184
+ }
185
+
186
+ const mergedPackageJson = mergeProjectPackageJson(existingPackageJson, projectPackageJson);
187
+ await writeTextFile(targetPath, JSON.stringify(mergedPackageJson, null, 2) + "\n");
188
+ }
189
+
135
190
  async function loadRenderState(projectDir, environment) {
136
191
  const statePath = path.join(projectDir, "offline", "S3TELocal", "render-state", `${environment}.json`);
137
192
  try {
@@ -268,7 +323,7 @@ export async function scaffoldProject(projectDir, options = {}) {
268
323
  }
269
324
  };
270
325
 
271
- await writeProjectFile(path.join(projectDir, "package.json"), JSON.stringify(projectPackageJson, null, 2) + "\n", force);
326
+ await writeProjectPackageJson(path.join(projectDir, "package.json"), projectPackageJson, force);
272
327
  await writeProjectFile(path.join(projectDir, "s3te.config.json"), JSON.stringify(config, null, 2) + "\n", force);
273
328
  await writeProjectFile(path.join(projectDir, "offline", "schemas", "s3te.config.schema.json"), JSON.stringify(schemaTemplate(), null, 2) + "\n", force);
274
329
  await writeProjectFile(path.join(projectDir, "app", "part", "head.part"), "<meta charset='utf-8'>\n<title>My S3TE Site</title>\n", force);