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 +93 -0
- package/dist/index.js +124 -77
- package/dist/templates/blank/pnpm-workspace.yaml +3 -7
- package/package.json +2 -2
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
|
|
10
|
-
import
|
|
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
|
|
29
|
-
import
|
|
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
|
-
|
|
35
|
-
for (const entry of
|
|
36
|
-
const srcPath =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
51
|
-
import
|
|
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 =
|
|
55
|
-
if (
|
|
56
|
-
const m = CONFIG_NAMESPACE_RE.exec(
|
|
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 =
|
|
60
|
-
if (
|
|
107
|
+
const manifestPath = path3.join(targetDir, "objectstack.manifest.json");
|
|
108
|
+
if (fs3.existsSync(manifestPath)) {
|
|
61
109
|
try {
|
|
62
|
-
const m = JSON.parse(
|
|
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 (!
|
|
71
|
-
for (const entry of
|
|
72
|
-
const full =
|
|
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 =
|
|
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)
|
|
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 =
|
|
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:
|
|
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
|
|
142
|
-
import
|
|
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 =
|
|
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(
|
|
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 =
|
|
221
|
+
const dockerfile = path4.join(targetDir, "Dockerfile");
|
|
174
222
|
let text;
|
|
175
223
|
try {
|
|
176
|
-
text =
|
|
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
|
-
|
|
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 =
|
|
223
|
-
var BUNDLED_TEMPLATES_DIR =
|
|
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 =
|
|
241
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
277
|
-
if (!
|
|
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 =
|
|
288
|
-
if (
|
|
327
|
+
const pkgPath = path5.join(targetDir, "package.json");
|
|
328
|
+
if (fs5.existsSync(pkgPath)) {
|
|
289
329
|
try {
|
|
290
|
-
const pkg = JSON.parse(
|
|
330
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
|
|
291
331
|
pkg.name = projectName;
|
|
292
332
|
syncObjectStackDeps(pkg, readCliVersion());
|
|
293
|
-
|
|
333
|
+
fs5.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
294
334
|
} catch {
|
|
295
335
|
}
|
|
296
336
|
}
|
|
297
|
-
const manifestPath =
|
|
298
|
-
if (
|
|
337
|
+
const manifestPath = path5.join(targetDir, "objectstack.manifest.json");
|
|
338
|
+
if (fs5.existsSync(manifestPath)) {
|
|
299
339
|
try {
|
|
300
|
-
const m = JSON.parse(
|
|
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
|
-
|
|
345
|
+
fs5.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
|
|
306
346
|
} catch {
|
|
307
347
|
}
|
|
308
348
|
}
|
|
309
|
-
const configPath =
|
|
310
|
-
if (
|
|
311
|
-
let cfg =
|
|
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
|
-
|
|
356
|
+
fs5.writeFileSync(configPath, cfg);
|
|
317
357
|
}
|
|
318
358
|
if (templateNamespace && namespace !== templateNamespace) {
|
|
319
|
-
const srcDir =
|
|
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 =
|
|
334
|
-
if (
|
|
335
|
-
let md =
|
|
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
|
-
|
|
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 =
|
|
382
|
+
const templatePath = path5.join(BUNDLED_TEMPLATES_DIR, "AGENTS.md");
|
|
343
383
|
let template;
|
|
344
384
|
try {
|
|
345
|
-
template =
|
|
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(
|
|
352
|
-
const copilotPath =
|
|
353
|
-
|
|
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(
|
|
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
|
-
|
|
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 ||
|
|
471
|
+
const projectName = name || path5.basename(cwd);
|
|
432
472
|
const namespace = sanitizeNamespace(projectName);
|
|
433
|
-
const targetDir = name ?
|
|
473
|
+
const targetDir = name ? path5.resolve(cwd, name) : cwd;
|
|
434
474
|
const isCurrentDir = targetDir === cwd;
|
|
435
|
-
const
|
|
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 &&
|
|
442
|
-
const existing =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
48
|
-
#
|
|
49
|
-
#
|
|
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.
|
|
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"
|