create-objectstack 17.2.0 → 17.3.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,98 @@
1
1
  # create-objectstack
2
2
 
3
+ ## 17.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 366f895: feat(auth): migrate `@better-auth/scim` from `1.7.0-rc.1` to stable `1.7.1` — the whole-model SCIM migration (#3653, epic #11632)
8
+
9
+ The stable line is the rc.2-lineage rewrite: the rc.1 `scimProvider` model,
10
+ `/scim/generate-token` endpoint and `storeSCIMToken` option no longer exist,
11
+ replaced by seven new models and a three-way connection contract. This lands
12
+ the migration atomically:
13
+
14
+ - **Seven new platform objects** back the stable models —
15
+ `sys_scim_connection_binding`, `sys_scim_group`, `sys_scim_group_member`,
16
+ `sys_scim_identity_tombstone`, `sys_scim_projection_grant`,
17
+ `sys_scim_subject`, `sys_scim_user` — bridged via `AUTH_MODEL_TO_PROTOCOL`,
18
+ registered in the platform-object-names registry, listed in
19
+ `BETTER_AUTH_MANAGED_OBJECTS`, and column-pinned by the parity gate (whose
20
+ `KNOWN_UNMAPPED_MODELS` shrinks to the empty set: the rc.1-era group
21
+ provisioning gap — IdP `/Groups` pushes hitting tables that did not exist —
22
+ is closed).
23
+ - **SCIM connections stay runtime data.** The stable constructor is satisfied
24
+ with an application-owned `authentication.verifyBearerToken` that resolves
25
+ the connection from a row at request time — not static boot config, and not
26
+ the upstream `managedConnections` catalog (deliberately not adopted).
27
+ - **ObjectStack owns SCIM credentials outright** (stable upstream stores no
28
+ credential at all): `sys_scim_connection_credential` plus
29
+ `scim-connection-service.ts` mint/digest/verify. At rest only an
30
+ HMAC-SHA-256 keyed by the deployment auth secret (base64url,
31
+ domain-separated) is stored — at parity or better than the rc.1 unsalted
32
+ SHA-256 — pinned by `credential-at-rest-posture.test.ts` including live
33
+ 401 paths for forged, revoked and expired bearers.
34
+ - **The ObjectQL better-auth adapter gains native transactions**
35
+ (`engine.transaction`, fail-closed on drivers without `beginTransaction`),
36
+ which stable scim requires by assertion for atomic provisioning writes.
37
+ - **Scaffold suppression retired**: the `@better-auth/scim>better-call`
38
+ `allowedVersions` entry (CLI renderer + blank template) is gone — stable
39
+ 1.7.1 peers `better-call@1.4.0` exactly — and its presence ratchets flipped
40
+ to absence pins. The `better-auth>better-sqlite3` and four
41
+ `@better-auth/utils` entries stay; their retirement conditions are separate
42
+ and unmet.
43
+ - The pin resolves **1.7.1 exactly** (not `^1.7.1`): 1.7.2 peers
44
+ `better-auth`/`@better-auth/core` at `^1.7.2`, which only the workspace
45
+ overrides' silencing would "satisfy" while the family is 1.7.1. Floating is
46
+ its own follow-up.
47
+
48
+ **Semver: minor, argued.** The rc.1 SCIM surface this replaces (generate-token
49
+ endpoint, rc.1 bearer tokens, `sys_scim_provider` rows) changes incompatibly —
50
+ but that surface is default-off (`OS_SCIM_ENABLED`), was shipped with a
51
+ documented "do not let the IdP push groups" boundary, and the maintainer ruled
52
+ (2026-08-25) that SCIM has no real customers and old data need not carry: the
53
+ one binding constraint is that an existing system upgrades smoothly, which it
54
+ does — every table the installed library can write exists at this version, and
55
+ SCIM-disabled deployments see no behavior change. A major would move the whole
56
+ fixed version group for a feature surface with zero consumers. Deployments
57
+ that had SCIM enabled must mint new connection credentials (digests are not
58
+ portable from rc.1 on any path — IdP token reissue is a migration-day
59
+ operator action regardless of semver level). `sys_scim_provider` itself is
60
+ NOT removed here; its retirement is tracked separately (#11757).
61
+
62
+ ### Patch Changes
63
+
64
+ - c6c7fec: fix(create-objectstack): stop reporting a failed pnpm probe as a deliberate npm choice (#11616)
65
+
66
+ `detectPackageManager()` was `try { execSync('pnpm --version') } catch { return
67
+ 'npm' }`, so every failure mode collapsed into one answer. `npm install` in the
68
+ scaffolder's output meant either *this machine has no pnpm* or *the probe
69
+ threw*, and nothing — no log line, no message — could tell the two apart.
70
+
71
+ That second case is reachable on an ordinary developer machine, not just in
72
+ theory: `pnpm --version` resolves through Corepack and therefore depends on the
73
+ directory it runs in. Measured on one machine, one binary, two directories —
74
+ `10.31.0` inside a repo that pins `packageManager`, `10.33.0` outside it, where
75
+ Corepack has to resolve, and may have to fetch, a version nothing pinned. A
76
+ user who has pnpm installed but is on a slow or offline network was silently
77
+ told to run npm.
78
+
79
+ The probe now reports why as well as what:
80
+
81
+ - `probe: 'ok'` — pnpm answered, so pnpm is used (unchanged, silent).
82
+ - `probe: 'absent'` — no pnpm on PATH at all, so npm is a real choice
83
+ (unchanged, silent).
84
+ - `probe: 'failed'` — pnpm **is** on PATH and the probe still threw. npm is
85
+ used exactly as before, and the run now says so, naming the underlying
86
+ failure: `pnpm is installed but \`pnpm --version\` failed (<reason>); using
87
+ npm as a fallback.`
88
+
89
+ **Which package manager a run uses is unchanged in all three cases** — it is
90
+ still pnpm if and only if the probe succeeded. The PATH lookup that separates
91
+ `absent` from `failed` runs only after the decision is already made and feeds
92
+ the message alone, so a miss there can change a warning's wording and never the
93
+ tool's behaviour. The only output that moves is one warning in a case that was
94
+ previously silent and wrong.
95
+
3
96
  ## 17.2.0
4
97
 
5
98
  ### Minor Changes
package/dist/index.js CHANGED
@@ -6,9 +6,57 @@ import {
6
6
  // src/index.ts
7
7
  import { Command } from "commander";
8
8
  import chalk2 from "chalk";
9
- import fs4 from "fs";
10
- import path4 from "path";
9
+ import fs5 from "fs";
10
+ import path5 from "path";
11
+ import { execSync as execSync2 } from "child_process";
12
+
13
+ // src/detect-package-manager.ts
14
+ import fs from "fs";
15
+ import path from "path";
11
16
  import { execSync } from "child_process";
17
+ function resolveOnPath(cmd, env = process.env) {
18
+ const raw = env.PATH ?? "";
19
+ if (!raw) return null;
20
+ const exts = process.platform === "win32" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
21
+ for (const dir of raw.split(path.delimiter)) {
22
+ if (!dir) continue;
23
+ for (const ext of exts) {
24
+ const candidate = path.join(dir, cmd + ext);
25
+ try {
26
+ if (fs.statSync(candidate).isFile()) return candidate;
27
+ } catch {
28
+ }
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ function probeFailureDetail(err) {
34
+ const e = err ?? {};
35
+ if (e.signal) return `killed by ${e.signal}`;
36
+ const stderr = e.stderr == null ? "" : String(e.stderr);
37
+ const firstLine = stderr.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
38
+ if (firstLine) return firstLine.length > 200 ? `${firstLine.slice(0, 197)}...` : firstLine;
39
+ if (typeof e.code === "string") return e.code;
40
+ if (typeof e.status === "number") return `exited ${e.status}`;
41
+ const msg = (e.message ?? "").split("\n")[0]?.trim();
42
+ return msg || "unknown error";
43
+ }
44
+ function defaultProbe() {
45
+ execSync("pnpm --version", { stdio: ["ignore", "ignore", "pipe"] });
46
+ }
47
+ function detectPackageManager(deps = {}) {
48
+ const probe = deps.probe ?? defaultProbe;
49
+ const pnpmOnPath = deps.pnpmOnPath ?? (() => resolveOnPath("pnpm") !== null);
50
+ try {
51
+ probe();
52
+ return { pm: "pnpm", probe: "ok" };
53
+ } catch (err) {
54
+ if (!pnpmOnPath()) return { pm: "npm", probe: "absent" };
55
+ return { pm: "npm", probe: "failed", detail: probeFailureDetail(err) };
56
+ }
57
+ }
58
+
59
+ // src/index.ts
12
60
  import { fileURLToPath } from "url";
13
61
 
14
62
  // src/pkg-utils.ts
@@ -25,41 +73,41 @@ function syncObjectStackDeps(pkg, version) {
25
73
  }
26
74
 
27
75
  // src/template-copy.ts
28
- import fs from "fs";
29
- import path from "path";
76
+ import fs2 from "fs";
77
+ import path2 from "path";
30
78
  var TEMPLATE_FILE_ALIASES = /* @__PURE__ */ new Map([
31
79
  ["_gitignore", ".gitignore"]
32
80
  ]);
33
81
  function copyDir(src, dest, collected, rel = "") {
34
- fs.mkdirSync(dest, { recursive: true });
35
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
36
- const srcPath = path.join(src, entry.name);
82
+ fs2.mkdirSync(dest, { recursive: true });
83
+ for (const entry of fs2.readdirSync(src, { withFileTypes: true })) {
84
+ const srcPath = path2.join(src, entry.name);
37
85
  const outName = entry.isFile() ? TEMPLATE_FILE_ALIASES.get(entry.name) ?? entry.name : entry.name;
38
- const destPath = path.join(dest, outName);
86
+ const destPath = path2.join(dest, outName);
39
87
  const relPath = rel ? `${rel}/${outName}` : outName;
40
88
  if (entry.isDirectory()) {
41
89
  copyDir(srcPath, destPath, collected, relPath);
42
90
  } else if (entry.isFile()) {
43
- fs.copyFileSync(srcPath, destPath);
91
+ fs2.copyFileSync(srcPath, destPath);
44
92
  collected.push(relPath);
45
93
  }
46
94
  }
47
95
  }
48
96
 
49
97
  // src/rewrite-identity.ts
50
- import fs2 from "fs";
51
- import path2 from "path";
98
+ import fs3 from "fs";
99
+ import path3 from "path";
52
100
  var CONFIG_NAMESPACE_RE = /\bnamespace:\s*(['"`])([a-z0-9_]+)\1/i;
53
101
  function readTemplateNamespace(targetDir) {
54
- const configPath = path2.join(targetDir, "objectstack.config.ts");
55
- if (fs2.existsSync(configPath)) {
56
- const m = CONFIG_NAMESPACE_RE.exec(fs2.readFileSync(configPath, "utf8"));
102
+ const configPath = path3.join(targetDir, "objectstack.config.ts");
103
+ if (fs3.existsSync(configPath)) {
104
+ const m = CONFIG_NAMESPACE_RE.exec(fs3.readFileSync(configPath, "utf8"));
57
105
  if (m) return m[2];
58
106
  }
59
- const manifestPath = path2.join(targetDir, "objectstack.manifest.json");
60
- if (fs2.existsSync(manifestPath)) {
107
+ const manifestPath = path3.join(targetDir, "objectstack.manifest.json");
108
+ if (fs3.existsSync(manifestPath)) {
61
109
  try {
62
- const m = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
110
+ const m = JSON.parse(fs3.readFileSync(manifestPath, "utf8"));
63
111
  if (typeof m.namespace === "string" && m.namespace) return m.namespace;
64
112
  } catch {
65
113
  }
@@ -67,9 +115,9 @@ function readTemplateNamespace(targetDir) {
67
115
  return void 0;
68
116
  }
69
117
  function tsFiles(dir, out = []) {
70
- if (!fs2.existsSync(dir)) return out;
71
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
72
- const full = path2.join(dir, entry.name);
118
+ if (!fs3.existsSync(dir)) return out;
119
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
120
+ const full = path3.join(dir, entry.name);
73
121
  if (entry.isDirectory()) {
74
122
  if (entry.name === "node_modules") continue;
75
123
  tsFiles(full, out);
@@ -83,7 +131,7 @@ var namePrefixRe = (ns, flags) => new RegExp(`(\\bname:\\s*)(['"\`])${ns}_([a-z0
83
131
  function rewriteObjectNamePrefix(dir, from, to) {
84
132
  let rewritten = 0;
85
133
  for (const file of tsFiles(dir)) {
86
- const before = fs2.readFileSync(file, "utf8");
134
+ const before = fs3.readFileSync(file, "utf8");
87
135
  const after = before.replace(
88
136
  namePrefixRe(from, "g"),
89
137
  (_m, prefix, q, rest) => {
@@ -91,7 +139,7 @@ function rewriteObjectNamePrefix(dir, from, to) {
91
139
  return `${prefix}${q}${to}_${rest}${q}`;
92
140
  }
93
141
  );
94
- if (after !== before) fs2.writeFileSync(file, after);
142
+ if (after !== before) fs3.writeFileSync(file, after);
95
143
  }
96
144
  return rewritten;
97
145
  }
@@ -99,11 +147,11 @@ function findStaleNamespacePrefixes(dir, oldNs) {
99
147
  const re = namePrefixRe(oldNs, "");
100
148
  const stale = [];
101
149
  for (const file of tsFiles(dir)) {
102
- const lines = fs2.readFileSync(file, "utf8").split("\n");
150
+ const lines = fs3.readFileSync(file, "utf8").split("\n");
103
151
  for (let i = 0; i < lines.length; i++) {
104
152
  if (re.test(lines[i])) {
105
153
  stale.push({
106
- file: path2.relative(dir, file),
154
+ file: path3.relative(dir, file),
107
155
  line: i + 1,
108
156
  text: lines[i].trim()
109
157
  });
@@ -138,13 +186,13 @@ function templateNames() {
138
186
  }
139
187
 
140
188
  // src/runtime-image.ts
141
- import fs3 from "fs";
142
- import path3 from "path";
189
+ import fs4 from "fs";
190
+ import path4 from "path";
143
191
  var RUNTIME_FROM_RE = /^FROM ghcr\.io\/objectstack-ai\/objectstack:([A-Za-z0-9_][A-Za-z0-9_.+-]*)[ \t]*$/;
144
192
  var PINNABLE_VERSION_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
145
193
  var PROSE_COMMENT_RE = /^#[ \t]+\S/;
146
194
  function readResolvedCliVersion(targetDir) {
147
- const pkgPath = path3.join(
195
+ const pkgPath = path4.join(
148
196
  targetDir,
149
197
  "node_modules",
150
198
  "@objectstack",
@@ -152,7 +200,7 @@ function readResolvedCliVersion(targetDir) {
152
200
  "package.json"
153
201
  );
154
202
  try {
155
- const version = JSON.parse(fs3.readFileSync(pkgPath, "utf8")).version;
203
+ const version = JSON.parse(fs4.readFileSync(pkgPath, "utf8")).version;
156
204
  return typeof version === "string" && PINNABLE_VERSION_RE.test(version) ? version : void 0;
157
205
  } catch {
158
206
  return void 0;
@@ -170,10 +218,10 @@ function pinRuntimeImage(targetDir, version) {
170
218
  if (!PINNABLE_VERSION_RE.test(version)) {
171
219
  return { pinned: false, reason: `'${version}' is not a pinnable version` };
172
220
  }
173
- const dockerfile = path3.join(targetDir, "Dockerfile");
221
+ const dockerfile = path4.join(targetDir, "Dockerfile");
174
222
  let text;
175
223
  try {
176
- text = fs3.readFileSync(dockerfile, "utf8");
224
+ text = fs4.readFileSync(dockerfile, "utf8");
177
225
  } catch {
178
226
  return { pinned: false, reason: "no Dockerfile in the scaffolded project" };
179
227
  }
@@ -193,7 +241,7 @@ function pinRuntimeImage(targetDir, version) {
193
241
  if (!check || check[1] !== version) {
194
242
  return { pinned: false, reason: "rewrite did not produce the pinned tag" };
195
243
  }
196
- fs3.writeFileSync(dockerfile, pinnedText);
244
+ fs4.writeFileSync(dockerfile, pinnedText);
197
245
  return { pinned: true, tag: version };
198
246
  }
199
247
 
@@ -219,8 +267,8 @@ function renderVersionBanner(version) {
219
267
 
220
268
  // src/index.ts
221
269
  var __filename2 = fileURLToPath(import.meta.url);
222
- var __dirname2 = path4.dirname(__filename2);
223
- var BUNDLED_TEMPLATES_DIR = path4.resolve(__dirname2, "templates");
270
+ var __dirname2 = path5.dirname(__filename2);
271
+ var BUNDLED_TEMPLATES_DIR = path5.resolve(__dirname2, "templates");
224
272
  function toTitleCase(str) {
225
273
  return str.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
226
274
  }
@@ -237,8 +285,8 @@ function sanitizeNamespace(name) {
237
285
  }
238
286
  function readCliVersion() {
239
287
  try {
240
- const pkgPath = path4.resolve(__dirname2, "..", "package.json");
241
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
288
+ const pkgPath = path5.resolve(__dirname2, "..", "package.json");
289
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
242
290
  return String(pkg.version || "0.0.0");
243
291
  } catch {
244
292
  return "0.0.0";
@@ -264,17 +312,9 @@ function printStep(msg) {
264
312
  function printWarning(msg) {
265
313
  console.log(chalk2.yellow(` \u26A0 ${msg}`));
266
314
  }
267
- function detectPackageManager() {
268
- try {
269
- execSync("pnpm --version", { stdio: "ignore" });
270
- return "pnpm";
271
- } catch {
272
- return "npm";
273
- }
274
- }
275
315
  function loadBundled(templateDir, targetDir) {
276
- const src = path4.join(BUNDLED_TEMPLATES_DIR, templateDir);
277
- if (!fs4.existsSync(src)) {
316
+ const src = path5.join(BUNDLED_TEMPLATES_DIR, templateDir);
317
+ if (!fs5.existsSync(src)) {
278
318
  throw new Error(`Bundled template missing on disk: ${src}`);
279
319
  }
280
320
  const collected = [];
@@ -284,39 +324,39 @@ function loadBundled(templateDir, targetDir) {
284
324
  function rewriteProjectIdentity(targetDir, projectName, namespace) {
285
325
  const title = toTitleCase(projectName);
286
326
  const templateNamespace = readTemplateNamespace(targetDir);
287
- const pkgPath = path4.join(targetDir, "package.json");
288
- if (fs4.existsSync(pkgPath)) {
327
+ const pkgPath = path5.join(targetDir, "package.json");
328
+ if (fs5.existsSync(pkgPath)) {
289
329
  try {
290
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
330
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
291
331
  pkg.name = projectName;
292
332
  syncObjectStackDeps(pkg, readCliVersion());
293
- fs4.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
333
+ fs5.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
294
334
  } catch {
295
335
  }
296
336
  }
297
- const manifestPath = path4.join(targetDir, "objectstack.manifest.json");
298
- if (fs4.existsSync(manifestPath)) {
337
+ const manifestPath = path5.join(targetDir, "objectstack.manifest.json");
338
+ if (fs5.existsSync(manifestPath)) {
299
339
  try {
300
- const m = JSON.parse(fs4.readFileSync(manifestPath, "utf8"));
340
+ const m = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
301
341
  m.name = projectName;
302
342
  m.displayName = title;
303
343
  if ("namespace" in m) m.namespace = namespace;
304
344
  delete m.description;
305
- fs4.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
345
+ fs5.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
306
346
  } catch {
307
347
  }
308
348
  }
309
- const configPath = path4.join(targetDir, "objectstack.config.ts");
310
- if (fs4.existsSync(configPath)) {
311
- let cfg = fs4.readFileSync(configPath, "utf8");
349
+ const configPath = path5.join(targetDir, "objectstack.config.ts");
350
+ if (fs5.existsSync(configPath)) {
351
+ let cfg = fs5.readFileSync(configPath, "utf8");
312
352
  cfg = cfg.replace(/(\bid:\s*)(['"`])[^'"`]*\2/, `$1$2${projectName}$2`);
313
353
  cfg = cfg.replace(/(\bnamespace:\s*)(['"`])[^'"`]*\2/, `$1$2${namespace}$2`);
314
354
  cfg = cfg.replace(/(\bname:\s*)(['"`])[^'"`]*\2/, `$1$2${title}$2`);
315
355
  cfg = cfg.replace(/^[ \t]*description:\s*(['"`])[^'"`]*\1,?\r?\n/m, "");
316
- fs4.writeFileSync(configPath, cfg);
356
+ fs5.writeFileSync(configPath, cfg);
317
357
  }
318
358
  if (templateNamespace && namespace !== templateNamespace) {
319
- const srcDir = path4.join(targetDir, "src");
359
+ const srcDir = path5.join(targetDir, "src");
320
360
  rewriteObjectNamePrefix(srcDir, templateNamespace, namespace);
321
361
  const stale = findStaleNamespacePrefixes(srcDir, templateNamespace);
322
362
  if (stale.length > 0) {
@@ -330,32 +370,32 @@ The generated project would fail 'objectstack build' on the \${namespace}_\${sho
330
370
  );
331
371
  }
332
372
  }
333
- const readmePath = path4.join(targetDir, "README.md");
334
- if (fs4.existsSync(readmePath)) {
335
- let md = fs4.readFileSync(readmePath, "utf8");
373
+ const readmePath = path5.join(targetDir, "README.md");
374
+ if (fs5.existsSync(readmePath)) {
375
+ let md = fs5.readFileSync(readmePath, "utf8");
336
376
  md = md.replace(/^#\s+.*$/m, `# ${title}`);
337
- fs4.writeFileSync(readmePath, md);
377
+ fs5.writeFileSync(readmePath, md);
338
378
  }
339
379
  writeAgentGuides(targetDir, title, projectName);
340
380
  }
341
381
  function writeAgentGuides(targetDir, title, projectName) {
342
- const templatePath = path4.join(BUNDLED_TEMPLATES_DIR, "AGENTS.md");
382
+ const templatePath = path5.join(BUNDLED_TEMPLATES_DIR, "AGENTS.md");
343
383
  let template;
344
384
  try {
345
- template = fs4.readFileSync(templatePath, "utf8");
385
+ template = fs5.readFileSync(templatePath, "utf8");
346
386
  } catch (err) {
347
387
  if (err?.code === "ENOENT") return;
348
388
  throw err;
349
389
  }
350
390
  const rendered = template.replace(/\{\{PROJECT_TITLE\}\}/g, title).replace(/\{\{PROJECT_NAME\}\}/g, projectName);
351
- writeIfAbsent(path4.join(targetDir, "AGENTS.md"), rendered);
352
- const copilotPath = path4.join(targetDir, ".github", "copilot-instructions.md");
353
- fs4.mkdirSync(path4.dirname(copilotPath), { recursive: true });
391
+ writeIfAbsent(path5.join(targetDir, "AGENTS.md"), rendered);
392
+ const copilotPath = path5.join(targetDir, ".github", "copilot-instructions.md");
393
+ fs5.mkdirSync(path5.dirname(copilotPath), { recursive: true });
354
394
  writeIfAbsent(copilotPath, rendered);
355
395
  }
356
396
  function topLevelNames(dir) {
357
397
  try {
358
- return new Set(fs4.readdirSync(dir));
398
+ return new Set(fs5.readdirSync(dir));
359
399
  } catch {
360
400
  return /* @__PURE__ */ new Set();
361
401
  }
@@ -398,7 +438,7 @@ function printCreatedSummary(targetDir, opts) {
398
438
  }
399
439
  function writeIfAbsent(filePath, contents) {
400
440
  try {
401
- fs4.writeFileSync(filePath, contents, { flag: "wx" });
441
+ fs5.writeFileSync(filePath, contents, { flag: "wx" });
402
442
  } catch (err) {
403
443
  if (err?.code !== "EEXIST") throw err;
404
444
  }
@@ -428,18 +468,25 @@ var program = new Command().name("create-objectstack").description("Create a new
428
468
  }
429
469
  const template = lookup.template;
430
470
  const cwd = process.cwd();
431
- const projectName = name || path4.basename(cwd);
471
+ const projectName = name || path5.basename(cwd);
432
472
  const namespace = sanitizeNamespace(projectName);
433
- const targetDir = name ? path4.resolve(cwd, name) : cwd;
473
+ const targetDir = name ? path5.resolve(cwd, name) : cwd;
434
474
  const isCurrentDir = targetDir === cwd;
435
- const pm = detectPackageManager();
475
+ const detected = detectPackageManager();
476
+ const pm = detected.pm;
477
+ if (detected.probe === "failed") {
478
+ printWarning(
479
+ `pnpm is installed but \`pnpm --version\` failed (${detected.detail}); using npm as a fallback. The commands below name npm because the probe did not answer, not because this project prefers it.`
480
+ );
481
+ console.log("");
482
+ }
436
483
  printKV("Environment", projectName);
437
484
  printKV("Namespace", namespace);
438
485
  printKV("Template", `${options.template} \u2014 ${template.description}`);
439
486
  printKV("Directory", targetDir);
440
487
  console.log("");
441
- if (!isCurrentDir && fs4.existsSync(targetDir)) {
442
- const existing = fs4.readdirSync(targetDir);
488
+ if (!isCurrentDir && fs5.existsSync(targetDir)) {
489
+ const existing = fs5.readdirSync(targetDir);
443
490
  if (existing.length > 0) {
444
491
  printError(`Directory already exists and is not empty: ${targetDir}`);
445
492
  process.exit(1);
@@ -447,7 +494,7 @@ var program = new Command().name("create-objectstack").description("Create a new
447
494
  }
448
495
  const targetWasEmpty = topLevelNames(targetDir).size === 0;
449
496
  try {
450
- fs4.mkdirSync(targetDir, { recursive: true });
497
+ fs5.mkdirSync(targetDir, { recursive: true });
451
498
  const createdFiles = loadBundled(template.source.dir, targetDir);
452
499
  rewriteProjectIdentity(targetDir, projectName, namespace);
453
500
  printSuccess(`Template files written (${createdFiles.length})`);
@@ -456,7 +503,7 @@ var program = new Command().name("create-objectstack").description("Create a new
456
503
  printStep("Installing dependencies...");
457
504
  let installed = false;
458
505
  try {
459
- execSync(`${pm} install`, { stdio: "inherit", cwd: targetDir });
506
+ execSync2(`${pm} install`, { stdio: "inherit", cwd: targetDir });
460
507
  installed = true;
461
508
  console.log("");
462
509
  } catch {
@@ -482,7 +529,7 @@ var program = new Command().name("create-objectstack").description("Create a new
482
529
  if (!options.skipInstall && !options.skipSkills) {
483
530
  printStep("Installing AI skills for your coding agent...");
484
531
  try {
485
- execSync("npx -y skills add objectstack-ai/objectstack/skills --all", {
532
+ execSync2("npx -y skills add objectstack-ai/objectstack/skills --all", {
486
533
  stdio: "inherit",
487
534
  cwd: targetDir
488
535
  });
@@ -44,12 +44,9 @@ allowBuilds:
44
44
  # sign-in and adapter find/update/delete — 1.7.1 behaves identically on
45
45
  # better-sqlite3 13.0.3 and on 12.11.1. The upstream range is stale.
46
46
  #
47
- # @better-auth/scim (held at a release candidate deliberately, not by
48
- # neglect) peers better-call at an exact 1.3.7, while better-auth itself
49
- # depends on 1.4.0. A better-auth plugin has to share the host's better-call
50
- # instance, so the single 1.4.0 copy is the correct resolution rather than a
51
- # skew to repair. This entry retires when SCIM moves off the rc: stable
52
- # @better-auth/scim 1.7.1 already peers better-call 1.4.0.
47
+ # (The '@better-auth/scim>better-call' entry that used to sit here retired
48
+ # with the scim release-candidate pin stable @better-auth/scim 1.7.1 peers
49
+ # better-call 1.4.0 exactly, so the skew it declared away is gone.)
53
50
  #
54
51
  # @better-auth/core, @better-auth/oauth-provider, @better-auth/scim and
55
52
  # @better-auth/sso each peer an exact @better-auth/utils 0.4.2, while the
@@ -75,7 +72,6 @@ allowBuilds:
75
72
  peerDependencyRules:
76
73
  allowedVersions:
77
74
  'better-auth>better-sqlite3': '13'
78
- '@better-auth/scim>better-call': '1.4.0'
79
75
  '@better-auth/core>@better-auth/utils': '0.5.0'
80
76
  '@better-auth/oauth-provider>@better-auth/utils': '0.5.0'
81
77
  '@better-auth/scim>@better-auth/utils': '0.5.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-objectstack",
3
- "version": "17.2.0",
3
+ "version": "17.3.0",
4
4
  "description": "Create a new ObjectStack project — npx create-objectstack",
5
5
  "bin": {
6
6
  "create-objectstack": "./bin/create-objectstack.js"
@@ -49,7 +49,7 @@
49
49
  "node": ">=22.0.0"
50
50
  },
51
51
  "scripts": {
52
- "build": "tsup",
52
+ "build": "tsup && node ../../scripts/check-dts-emitted.mjs",
53
53
  "typecheck": "tsc --noEmit",
54
54
  "dev": "tsup --watch",
55
55
  "test": "vitest run"