create-objectstack 17.2.0 → 17.4.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 +190 -0
- package/README.md +6 -3
- package/dist/index.js +157 -81
- package/dist/templates/AGENTS.md +9 -3
- package/dist/templates/blank/.github/workflows/ci.yml +67 -0
- package/dist/templates/blank/README.md +9 -2
- package/dist/templates/blank/package.json +2 -1
- package/dist/templates/blank/pnpm-workspace.yaml +3 -7
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,195 @@
|
|
|
1
1
|
# create-objectstack
|
|
2
2
|
|
|
3
|
+
## 17.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4998efa: Scaffolded projects now ship a CI workflow, and a `lint` script for it to run.
|
|
8
|
+
The blank template carries `.github/workflows/ci.yml` — one job, on `push` and
|
|
9
|
+
`pull_request`: checkout, pnpm, Node 22, `pnpm install --frozen-lockfile`, then
|
|
10
|
+
`pnpm validate`, `pnpm lint` and `pnpm typecheck`.
|
|
11
|
+
|
|
12
|
+
`lint` is new to the template (`objectstack lint`). It is not a second spelling
|
|
13
|
+
of `validate`: the two share the authoring-rule engine but only `lint` runs the
|
|
14
|
+
hook-body lowering check, which catches handlers that have silently stopped
|
|
15
|
+
lowering to metadata-only bodies — a change of deployment shape produced by a
|
|
16
|
+
refactor that looks like tidying.
|
|
17
|
+
|
|
18
|
+
The scaffolder already created `.github/` at runtime for a single file
|
|
19
|
+
(`copilot-instructions.md`) while the template's gates shipped as npm scripts
|
|
20
|
+
nothing ever ran, so a fresh project started with no CI at all — and ObjectStack
|
|
21
|
+
metadata mistakes fail silently at runtime, which makes `objectstack validate`
|
|
22
|
+
the only place they surface early. That gate is now unskippable for a human and
|
|
23
|
+
for an AI agent authoring metadata in the project, instead of advisory.
|
|
24
|
+
|
|
25
|
+
Existing projects are unaffected; copy the file from a fresh scaffold to adopt
|
|
26
|
+
it.
|
|
27
|
+
- 813d6c5: `npx create-objectstack` now declares the same TypeScript range as `os init` and
|
|
28
|
+
`os create`, and the value is generated rather than restated.
|
|
29
|
+
|
|
30
|
+
Three scaffolders write a new project's `package.json`, and the range that
|
|
31
|
+
decides whether that project type-checks at all had split: `os init` and
|
|
32
|
+
`os create` emitted `typescript: ^5.3.0` from a shared emission policy, while
|
|
33
|
+
this package's bundled template carried `^6.0.0`. Two projects created the same
|
|
34
|
+
day got different TypeScript **majors** depending on which documented entry
|
|
35
|
+
point the reader followed.
|
|
36
|
+
|
|
37
|
+
- **What changed for a scaffolded project.** Its declared `typescript`
|
|
38
|
+
devDependency floor moves from `^6.0.0` to `^5.3.0`. Both resolve to the same
|
|
39
|
+
installed compiler on a fresh install; what moves is the floor the project
|
|
40
|
+
**declares**, and a floor is a support promise. `^5.3.0` is the promise the
|
|
41
|
+
docs already make — "ObjectStack works with TypeScript 5.3+" on the getting
|
|
42
|
+
started page, "TypeScript 5.3.0 or later" in the deployment troubleshooting
|
|
43
|
+
page — and it is measured rather than assumed: TypeScript 5.3.3 type-checks
|
|
44
|
+
every shape these scaffolders emit with results identical to 6.0.3. The repo's
|
|
45
|
+
own `typescript@^6.0.3` devDependency is deliberately not this value; the same
|
|
46
|
+
doc sentence states both halves ("…but the project itself is built and tested
|
|
47
|
+
against TypeScript 6.x"). `engines.pnpm` was already in agreement and is now
|
|
48
|
+
held there by the same mechanism.
|
|
49
|
+
- **Why the value is generated.** This package cannot import from
|
|
50
|
+
`@objectstack/cli`: the dependency edge runs the other way, and the `npx`
|
|
51
|
+
package must not pull the CLI's package closure. So the values are stamped
|
|
52
|
+
into the bundled template at build time by
|
|
53
|
+
`scripts/sync-scaffold-emission-policy.mjs`, read out of the same
|
|
54
|
+
`SCAFFOLD_*` constants the other two scaffolders import, and
|
|
55
|
+
`pnpm check:scaffold-emission-policy` reddens the moment the inlined values
|
|
56
|
+
disagree with that source. Editing the two into agreement by hand would have
|
|
57
|
+
left them free to diverge again on the next move, silently, for the same
|
|
58
|
+
structural reason — which is how they diverged the first time.
|
|
59
|
+
- fd75728: Scaffolded projects now install the AI skills bundle for **one** agent runtime
|
|
60
|
+
instead of every runtime the skills CLI knows, so the bundle is committed once.
|
|
61
|
+
|
|
62
|
+
**Route B of the two the card offered was taken**, and the choice was measured
|
|
63
|
+
rather than argued. Against `skills@1.5.23` and the 11-skill catalog, the old
|
|
64
|
+
`--all` (shorthand for `--skill '*' --agent '*' -y`) wrote the same bundle to
|
|
65
|
+
three destinations — `.agents/` (46 real files, 604,102 B), `agent/` (46 real
|
|
66
|
+
files, 602,682 B, identical bodies with re-serialised frontmatter) and
|
|
67
|
+
`.claude/` (11 symlinks into `.agents/`). The template's `.gitignore` excluded
|
|
68
|
+
none of it, so a new project's first `git add -A` staged 22 `SKILL.md` paths
|
|
69
|
+
plus 11 symlinks. That reached the initial commit of a real app before anyone
|
|
70
|
+
noticed.
|
|
71
|
+
|
|
72
|
+
The scaffolder now runs
|
|
73
|
+
`npx skills add objectstack-ai/objectstack/skills --skill '*' --agent claude-code -y`,
|
|
74
|
+
which writes 46 real files to `.claude/skills/` and nothing else: 11 staged
|
|
75
|
+
`SKILL.md` paths, no symlinks, and a clone of that commit has readable skill
|
|
76
|
+
files on every platform.
|
|
77
|
+
|
|
78
|
+
Route A (keep `--all`, exclude the duplicates in the template `_gitignore`) was
|
|
79
|
+
built and cloned, not reasoned about, and both of its shapes were rejected.
|
|
80
|
+
Ignoring `.agents/` and `agent/` while committing `.claude/` gives a fresh
|
|
81
|
+
cloner 11 dangling symlinks and zero readable `SKILL.md`. Ignoring only
|
|
82
|
+
`agent/` works on POSIX but commits 11 symlinks that a `core.symlinks=false`
|
|
83
|
+
clone — git-for-Windows' default — materialises as ordinary files whose whole
|
|
84
|
+
content is the link target. `--all --copy`, the other way to make `.claude/`
|
|
85
|
+
real, fans out to 56 destination directories totalling 33.8 MB. A denylist is
|
|
86
|
+
also the wrong shape regardless of which paths it names: this package does not
|
|
87
|
+
choose the destination set, the skills CLI does, and it moves with that
|
|
88
|
+
package's releases.
|
|
89
|
+
|
|
90
|
+
The cost is the multi-runtime default, and it is paid in the open: the closing
|
|
91
|
+
summary now always prints an **AI Skills** block naming where the bundle landed
|
|
92
|
+
and the one-line command for any other runtime, one agent at a time. The
|
|
93
|
+
bundle is identical whichever agent is named.
|
|
94
|
+
|
|
95
|
+
Existing projects are unaffected. To shrink one that already carries the
|
|
96
|
+
triplicate, delete `.agents/` and `agent/` and re-run the single-agent command
|
|
97
|
+
above; `skills-lock.json` records source and hash, not paths, so it does not
|
|
98
|
+
change.
|
|
99
|
+
|
|
100
|
+
## 17.3.0
|
|
101
|
+
|
|
102
|
+
### Minor Changes
|
|
103
|
+
|
|
104
|
+
- 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)
|
|
105
|
+
|
|
106
|
+
The stable line is the rc.2-lineage rewrite: the rc.1 `scimProvider` model,
|
|
107
|
+
`/scim/generate-token` endpoint and `storeSCIMToken` option no longer exist,
|
|
108
|
+
replaced by seven new models and a three-way connection contract. This lands
|
|
109
|
+
the migration atomically:
|
|
110
|
+
|
|
111
|
+
- **Seven new platform objects** back the stable models —
|
|
112
|
+
`sys_scim_connection_binding`, `sys_scim_group`, `sys_scim_group_member`,
|
|
113
|
+
`sys_scim_identity_tombstone`, `sys_scim_projection_grant`,
|
|
114
|
+
`sys_scim_subject`, `sys_scim_user` — bridged via `AUTH_MODEL_TO_PROTOCOL`,
|
|
115
|
+
registered in the platform-object-names registry, listed in
|
|
116
|
+
`BETTER_AUTH_MANAGED_OBJECTS`, and column-pinned by the parity gate (whose
|
|
117
|
+
`KNOWN_UNMAPPED_MODELS` shrinks to the empty set: the rc.1-era group
|
|
118
|
+
provisioning gap — IdP `/Groups` pushes hitting tables that did not exist —
|
|
119
|
+
is closed).
|
|
120
|
+
- **SCIM connections stay runtime data.** The stable constructor is satisfied
|
|
121
|
+
with an application-owned `authentication.verifyBearerToken` that resolves
|
|
122
|
+
the connection from a row at request time — not static boot config, and not
|
|
123
|
+
the upstream `managedConnections` catalog (deliberately not adopted).
|
|
124
|
+
- **ObjectStack owns SCIM credentials outright** (stable upstream stores no
|
|
125
|
+
credential at all): `sys_scim_connection_credential` plus
|
|
126
|
+
`scim-connection-service.ts` mint/digest/verify. At rest only an
|
|
127
|
+
HMAC-SHA-256 keyed by the deployment auth secret (base64url,
|
|
128
|
+
domain-separated) is stored — at parity or better than the rc.1 unsalted
|
|
129
|
+
SHA-256 — pinned by `credential-at-rest-posture.test.ts` including live
|
|
130
|
+
401 paths for forged, revoked and expired bearers.
|
|
131
|
+
- **The ObjectQL better-auth adapter gains native transactions**
|
|
132
|
+
(`engine.transaction`, fail-closed on drivers without `beginTransaction`),
|
|
133
|
+
which stable scim requires by assertion for atomic provisioning writes.
|
|
134
|
+
- **Scaffold suppression retired**: the `@better-auth/scim>better-call`
|
|
135
|
+
`allowedVersions` entry (CLI renderer + blank template) is gone — stable
|
|
136
|
+
1.7.1 peers `better-call@1.4.0` exactly — and its presence ratchets flipped
|
|
137
|
+
to absence pins. The `better-auth>better-sqlite3` and four
|
|
138
|
+
`@better-auth/utils` entries stay; their retirement conditions are separate
|
|
139
|
+
and unmet.
|
|
140
|
+
- The pin resolves **1.7.1 exactly** (not `^1.7.1`): 1.7.2 peers
|
|
141
|
+
`better-auth`/`@better-auth/core` at `^1.7.2`, which only the workspace
|
|
142
|
+
overrides' silencing would "satisfy" while the family is 1.7.1. Floating is
|
|
143
|
+
its own follow-up.
|
|
144
|
+
|
|
145
|
+
**Semver: minor, argued.** The rc.1 SCIM surface this replaces (generate-token
|
|
146
|
+
endpoint, rc.1 bearer tokens, `sys_scim_provider` rows) changes incompatibly —
|
|
147
|
+
but that surface is default-off (`OS_SCIM_ENABLED`), was shipped with a
|
|
148
|
+
documented "do not let the IdP push groups" boundary, and the maintainer ruled
|
|
149
|
+
(2026-08-25) that SCIM has no real customers and old data need not carry: the
|
|
150
|
+
one binding constraint is that an existing system upgrades smoothly, which it
|
|
151
|
+
does — every table the installed library can write exists at this version, and
|
|
152
|
+
SCIM-disabled deployments see no behavior change. A major would move the whole
|
|
153
|
+
fixed version group for a feature surface with zero consumers. Deployments
|
|
154
|
+
that had SCIM enabled must mint new connection credentials (digests are not
|
|
155
|
+
portable from rc.1 on any path — IdP token reissue is a migration-day
|
|
156
|
+
operator action regardless of semver level). `sys_scim_provider` itself is
|
|
157
|
+
NOT removed here; its retirement is tracked separately (#11757).
|
|
158
|
+
|
|
159
|
+
### Patch Changes
|
|
160
|
+
|
|
161
|
+
- c6c7fec: fix(create-objectstack): stop reporting a failed pnpm probe as a deliberate npm choice (#11616)
|
|
162
|
+
|
|
163
|
+
`detectPackageManager()` was `try { execSync('pnpm --version') } catch { return
|
|
164
|
+
'npm' }`, so every failure mode collapsed into one answer. `npm install` in the
|
|
165
|
+
scaffolder's output meant either *this machine has no pnpm* or *the probe
|
|
166
|
+
threw*, and nothing — no log line, no message — could tell the two apart.
|
|
167
|
+
|
|
168
|
+
That second case is reachable on an ordinary developer machine, not just in
|
|
169
|
+
theory: `pnpm --version` resolves through Corepack and therefore depends on the
|
|
170
|
+
directory it runs in. Measured on one machine, one binary, two directories —
|
|
171
|
+
`10.31.0` inside a repo that pins `packageManager`, `10.33.0` outside it, where
|
|
172
|
+
Corepack has to resolve, and may have to fetch, a version nothing pinned. A
|
|
173
|
+
user who has pnpm installed but is on a slow or offline network was silently
|
|
174
|
+
told to run npm.
|
|
175
|
+
|
|
176
|
+
The probe now reports why as well as what:
|
|
177
|
+
|
|
178
|
+
- `probe: 'ok'` — pnpm answered, so pnpm is used (unchanged, silent).
|
|
179
|
+
- `probe: 'absent'` — no pnpm on PATH at all, so npm is a real choice
|
|
180
|
+
(unchanged, silent).
|
|
181
|
+
- `probe: 'failed'` — pnpm **is** on PATH and the probe still threw. npm is
|
|
182
|
+
used exactly as before, and the run now says so, naming the underlying
|
|
183
|
+
failure: `pnpm is installed but \`pnpm --version\` failed (<reason>); using
|
|
184
|
+
npm as a fallback.`
|
|
185
|
+
|
|
186
|
+
**Which package manager a run uses is unchanged in all three cases** — it is
|
|
187
|
+
still pnpm if and only if the probe succeeded. The PATH lookup that separates
|
|
188
|
+
`absent` from `failed` runs only after the decision is already made and feeds
|
|
189
|
+
the message alone, so a miss there can change a warning's wording and never the
|
|
190
|
+
tool's behaviour. The only output that moves is one warning in a case that was
|
|
191
|
+
previously silent and wrong.
|
|
192
|
+
|
|
3
193
|
## 17.2.0
|
|
4
194
|
|
|
5
195
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -56,9 +56,12 @@ you so rather than failing as an unknown template.
|
|
|
56
56
|
every object name in the template is re-prefixed to match
|
|
57
57
|
(`blank_note` → `my_app_note`).
|
|
58
58
|
2. Installs dependencies (pnpm if available, otherwise npm).
|
|
59
|
-
3. Installs the ObjectStack AI skills bundle for coding
|
|
60
|
-
|
|
61
|
-
curated `skills/` catalog
|
|
59
|
+
3. Installs the ObjectStack AI skills bundle for **one** coding agent —
|
|
60
|
+
`npx skills add objectstack-ai/objectstack/skills --skill '*' --agent claude-code -y`,
|
|
61
|
+
scoped to the curated `skills/` catalog. One agent, not `--all`, so the
|
|
62
|
+
bundle lands in exactly one directory (`.claude/skills/`) and a scaffolded
|
|
63
|
+
project commits it once; `src/skills-install.ts` carries the measurement.
|
|
64
|
+
The closing summary prints the same command for any other runtime.
|
|
62
65
|
4. Writes `AGENTS.md` and `.github/copilot-instructions.md` with the project
|
|
63
66
|
conventions — unless the template ships its own.
|
|
64
67
|
|
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,10 +241,23 @@ 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
|
|
|
248
|
+
// src/skills-install.ts
|
|
249
|
+
var SKILLS_CATALOG = "objectstack-ai/objectstack/skills";
|
|
250
|
+
var DEFAULT_SKILLS_AGENT = "claude-code";
|
|
251
|
+
var DEFAULT_SKILLS_DIR = ".claude/skills/";
|
|
252
|
+
var EXAMPLE_OTHER_AGENT = "codex";
|
|
253
|
+
function skillsAddArgs(agent) {
|
|
254
|
+
return `skills add ${SKILLS_CATALOG} --skill '*' --agent ${agent} -y`;
|
|
255
|
+
}
|
|
256
|
+
var SKILLS_INSTALL_COMMAND = `npx -y ${skillsAddArgs(DEFAULT_SKILLS_AGENT)}`;
|
|
257
|
+
function skillsInstallHint(agent) {
|
|
258
|
+
return `npx ${skillsAddArgs(agent)}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
200
261
|
// src/banner.ts
|
|
201
262
|
import chalk from "chalk";
|
|
202
263
|
var PREFIX = " \u25C6 Create ObjectStack ";
|
|
@@ -219,8 +280,8 @@ function renderVersionBanner(version) {
|
|
|
219
280
|
|
|
220
281
|
// src/index.ts
|
|
221
282
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
222
|
-
var __dirname2 =
|
|
223
|
-
var BUNDLED_TEMPLATES_DIR =
|
|
283
|
+
var __dirname2 = path5.dirname(__filename2);
|
|
284
|
+
var BUNDLED_TEMPLATES_DIR = path5.resolve(__dirname2, "templates");
|
|
224
285
|
function toTitleCase(str) {
|
|
225
286
|
return str.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
226
287
|
}
|
|
@@ -237,8 +298,8 @@ function sanitizeNamespace(name) {
|
|
|
237
298
|
}
|
|
238
299
|
function readCliVersion() {
|
|
239
300
|
try {
|
|
240
|
-
const pkgPath =
|
|
241
|
-
const pkg = JSON.parse(
|
|
301
|
+
const pkgPath = path5.resolve(__dirname2, "..", "package.json");
|
|
302
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
|
|
242
303
|
return String(pkg.version || "0.0.0");
|
|
243
304
|
} catch {
|
|
244
305
|
return "0.0.0";
|
|
@@ -264,17 +325,9 @@ function printStep(msg) {
|
|
|
264
325
|
function printWarning(msg) {
|
|
265
326
|
console.log(chalk2.yellow(` \u26A0 ${msg}`));
|
|
266
327
|
}
|
|
267
|
-
function detectPackageManager() {
|
|
268
|
-
try {
|
|
269
|
-
execSync("pnpm --version", { stdio: "ignore" });
|
|
270
|
-
return "pnpm";
|
|
271
|
-
} catch {
|
|
272
|
-
return "npm";
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
328
|
function loadBundled(templateDir, targetDir) {
|
|
276
|
-
const src =
|
|
277
|
-
if (!
|
|
329
|
+
const src = path5.join(BUNDLED_TEMPLATES_DIR, templateDir);
|
|
330
|
+
if (!fs5.existsSync(src)) {
|
|
278
331
|
throw new Error(`Bundled template missing on disk: ${src}`);
|
|
279
332
|
}
|
|
280
333
|
const collected = [];
|
|
@@ -284,39 +337,39 @@ function loadBundled(templateDir, targetDir) {
|
|
|
284
337
|
function rewriteProjectIdentity(targetDir, projectName, namespace) {
|
|
285
338
|
const title = toTitleCase(projectName);
|
|
286
339
|
const templateNamespace = readTemplateNamespace(targetDir);
|
|
287
|
-
const pkgPath =
|
|
288
|
-
if (
|
|
340
|
+
const pkgPath = path5.join(targetDir, "package.json");
|
|
341
|
+
if (fs5.existsSync(pkgPath)) {
|
|
289
342
|
try {
|
|
290
|
-
const pkg = JSON.parse(
|
|
343
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
|
|
291
344
|
pkg.name = projectName;
|
|
292
345
|
syncObjectStackDeps(pkg, readCliVersion());
|
|
293
|
-
|
|
346
|
+
fs5.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
294
347
|
} catch {
|
|
295
348
|
}
|
|
296
349
|
}
|
|
297
|
-
const manifestPath =
|
|
298
|
-
if (
|
|
350
|
+
const manifestPath = path5.join(targetDir, "objectstack.manifest.json");
|
|
351
|
+
if (fs5.existsSync(manifestPath)) {
|
|
299
352
|
try {
|
|
300
|
-
const m = JSON.parse(
|
|
353
|
+
const m = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
|
|
301
354
|
m.name = projectName;
|
|
302
355
|
m.displayName = title;
|
|
303
356
|
if ("namespace" in m) m.namespace = namespace;
|
|
304
357
|
delete m.description;
|
|
305
|
-
|
|
358
|
+
fs5.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
|
|
306
359
|
} catch {
|
|
307
360
|
}
|
|
308
361
|
}
|
|
309
|
-
const configPath =
|
|
310
|
-
if (
|
|
311
|
-
let cfg =
|
|
362
|
+
const configPath = path5.join(targetDir, "objectstack.config.ts");
|
|
363
|
+
if (fs5.existsSync(configPath)) {
|
|
364
|
+
let cfg = fs5.readFileSync(configPath, "utf8");
|
|
312
365
|
cfg = cfg.replace(/(\bid:\s*)(['"`])[^'"`]*\2/, `$1$2${projectName}$2`);
|
|
313
366
|
cfg = cfg.replace(/(\bnamespace:\s*)(['"`])[^'"`]*\2/, `$1$2${namespace}$2`);
|
|
314
367
|
cfg = cfg.replace(/(\bname:\s*)(['"`])[^'"`]*\2/, `$1$2${title}$2`);
|
|
315
368
|
cfg = cfg.replace(/^[ \t]*description:\s*(['"`])[^'"`]*\1,?\r?\n/m, "");
|
|
316
|
-
|
|
369
|
+
fs5.writeFileSync(configPath, cfg);
|
|
317
370
|
}
|
|
318
371
|
if (templateNamespace && namespace !== templateNamespace) {
|
|
319
|
-
const srcDir =
|
|
372
|
+
const srcDir = path5.join(targetDir, "src");
|
|
320
373
|
rewriteObjectNamePrefix(srcDir, templateNamespace, namespace);
|
|
321
374
|
const stale = findStaleNamespacePrefixes(srcDir, templateNamespace);
|
|
322
375
|
if (stale.length > 0) {
|
|
@@ -330,32 +383,32 @@ The generated project would fail 'objectstack build' on the \${namespace}_\${sho
|
|
|
330
383
|
);
|
|
331
384
|
}
|
|
332
385
|
}
|
|
333
|
-
const readmePath =
|
|
334
|
-
if (
|
|
335
|
-
let md =
|
|
386
|
+
const readmePath = path5.join(targetDir, "README.md");
|
|
387
|
+
if (fs5.existsSync(readmePath)) {
|
|
388
|
+
let md = fs5.readFileSync(readmePath, "utf8");
|
|
336
389
|
md = md.replace(/^#\s+.*$/m, `# ${title}`);
|
|
337
|
-
|
|
390
|
+
fs5.writeFileSync(readmePath, md);
|
|
338
391
|
}
|
|
339
392
|
writeAgentGuides(targetDir, title, projectName);
|
|
340
393
|
}
|
|
341
394
|
function writeAgentGuides(targetDir, title, projectName) {
|
|
342
|
-
const templatePath =
|
|
395
|
+
const templatePath = path5.join(BUNDLED_TEMPLATES_DIR, "AGENTS.md");
|
|
343
396
|
let template;
|
|
344
397
|
try {
|
|
345
|
-
template =
|
|
398
|
+
template = fs5.readFileSync(templatePath, "utf8");
|
|
346
399
|
} catch (err) {
|
|
347
400
|
if (err?.code === "ENOENT") return;
|
|
348
401
|
throw err;
|
|
349
402
|
}
|
|
350
403
|
const rendered = template.replace(/\{\{PROJECT_TITLE\}\}/g, title).replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
351
|
-
writeIfAbsent(
|
|
352
|
-
const copilotPath =
|
|
353
|
-
|
|
404
|
+
writeIfAbsent(path5.join(targetDir, "AGENTS.md"), rendered);
|
|
405
|
+
const copilotPath = path5.join(targetDir, ".github", "copilot-instructions.md");
|
|
406
|
+
fs5.mkdirSync(path5.dirname(copilotPath), { recursive: true });
|
|
354
407
|
writeIfAbsent(copilotPath, rendered);
|
|
355
408
|
}
|
|
356
409
|
function topLevelNames(dir) {
|
|
357
410
|
try {
|
|
358
|
-
return new Set(
|
|
411
|
+
return new Set(fs5.readdirSync(dir));
|
|
359
412
|
} catch {
|
|
360
413
|
return /* @__PURE__ */ new Set();
|
|
361
414
|
}
|
|
@@ -398,7 +451,7 @@ function printCreatedSummary(targetDir, opts) {
|
|
|
398
451
|
}
|
|
399
452
|
function writeIfAbsent(filePath, contents) {
|
|
400
453
|
try {
|
|
401
|
-
|
|
454
|
+
fs5.writeFileSync(filePath, contents, { flag: "wx" });
|
|
402
455
|
} catch (err) {
|
|
403
456
|
if (err?.code !== "EEXIST") throw err;
|
|
404
457
|
}
|
|
@@ -428,18 +481,25 @@ var program = new Command().name("create-objectstack").description("Create a new
|
|
|
428
481
|
}
|
|
429
482
|
const template = lookup.template;
|
|
430
483
|
const cwd = process.cwd();
|
|
431
|
-
const projectName = name ||
|
|
484
|
+
const projectName = name || path5.basename(cwd);
|
|
432
485
|
const namespace = sanitizeNamespace(projectName);
|
|
433
|
-
const targetDir = name ?
|
|
486
|
+
const targetDir = name ? path5.resolve(cwd, name) : cwd;
|
|
434
487
|
const isCurrentDir = targetDir === cwd;
|
|
435
|
-
const
|
|
488
|
+
const detected = detectPackageManager();
|
|
489
|
+
const pm = detected.pm;
|
|
490
|
+
if (detected.probe === "failed") {
|
|
491
|
+
printWarning(
|
|
492
|
+
`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.`
|
|
493
|
+
);
|
|
494
|
+
console.log("");
|
|
495
|
+
}
|
|
436
496
|
printKV("Environment", projectName);
|
|
437
497
|
printKV("Namespace", namespace);
|
|
438
498
|
printKV("Template", `${options.template} \u2014 ${template.description}`);
|
|
439
499
|
printKV("Directory", targetDir);
|
|
440
500
|
console.log("");
|
|
441
|
-
if (!isCurrentDir &&
|
|
442
|
-
const existing =
|
|
501
|
+
if (!isCurrentDir && fs5.existsSync(targetDir)) {
|
|
502
|
+
const existing = fs5.readdirSync(targetDir);
|
|
443
503
|
if (existing.length > 0) {
|
|
444
504
|
printError(`Directory already exists and is not empty: ${targetDir}`);
|
|
445
505
|
process.exit(1);
|
|
@@ -447,7 +507,7 @@ var program = new Command().name("create-objectstack").description("Create a new
|
|
|
447
507
|
}
|
|
448
508
|
const targetWasEmpty = topLevelNames(targetDir).size === 0;
|
|
449
509
|
try {
|
|
450
|
-
|
|
510
|
+
fs5.mkdirSync(targetDir, { recursive: true });
|
|
451
511
|
const createdFiles = loadBundled(template.source.dir, targetDir);
|
|
452
512
|
rewriteProjectIdentity(targetDir, projectName, namespace);
|
|
453
513
|
printSuccess(`Template files written (${createdFiles.length})`);
|
|
@@ -456,7 +516,7 @@ var program = new Command().name("create-objectstack").description("Create a new
|
|
|
456
516
|
printStep("Installing dependencies...");
|
|
457
517
|
let installed = false;
|
|
458
518
|
try {
|
|
459
|
-
|
|
519
|
+
execSync2(`${pm} install`, { stdio: "inherit", cwd: targetDir });
|
|
460
520
|
installed = true;
|
|
461
521
|
console.log("");
|
|
462
522
|
} catch {
|
|
@@ -479,17 +539,20 @@ var program = new Command().name("create-objectstack").description("Create a new
|
|
|
479
539
|
}
|
|
480
540
|
}
|
|
481
541
|
const beforeSkills = topLevelNames(targetDir);
|
|
542
|
+
let skillsInstalled = false;
|
|
482
543
|
if (!options.skipInstall && !options.skipSkills) {
|
|
483
544
|
printStep("Installing AI skills for your coding agent...");
|
|
484
545
|
try {
|
|
485
|
-
|
|
546
|
+
execSync2(SKILLS_INSTALL_COMMAND, {
|
|
486
547
|
stdio: "inherit",
|
|
487
548
|
cwd: targetDir
|
|
488
549
|
});
|
|
550
|
+
skillsInstalled = true;
|
|
489
551
|
console.log("");
|
|
490
552
|
} catch {
|
|
491
553
|
printWarning(
|
|
492
|
-
|
|
554
|
+
`Skills installation skipped. Run manually:
|
|
555
|
+
${skillsInstallHint(DEFAULT_SKILLS_AGENT)}`
|
|
493
556
|
);
|
|
494
557
|
console.log("");
|
|
495
558
|
}
|
|
@@ -513,10 +576,23 @@ var program = new Command().name("create-objectstack").description("Create a new
|
|
|
513
576
|
console.log(chalk2.dim(` ${devLabel.padEnd(labelWidth)}# Start development server`));
|
|
514
577
|
console.log(chalk2.dim(` ${validateLabel.padEnd(labelWidth)}# Verify metadata: schema + predicates + bindings`));
|
|
515
578
|
console.log(chalk2.dim(` ${" ".repeat(labelWidth)}# (run after every metadata edit \u2014 see AGENTS.md)`));
|
|
516
|
-
|
|
517
|
-
|
|
579
|
+
console.log("");
|
|
580
|
+
if (skillsInstalled) {
|
|
581
|
+
console.log(chalk2.bold(" AI Skills:"));
|
|
582
|
+
console.log(
|
|
583
|
+
chalk2.dim(
|
|
584
|
+
` Installed for ${DEFAULT_SKILLS_AGENT} \u2192 ${DEFAULT_SKILLS_DIR} \u2014 commit it with your project.`
|
|
585
|
+
)
|
|
586
|
+
);
|
|
587
|
+
console.log(chalk2.dim(" Using a different agent? Add it one runtime at a time:"));
|
|
588
|
+
console.log(chalk2.dim(` ${skillsInstallHint(EXAMPLE_OTHER_AGENT)}`));
|
|
589
|
+
console.log(chalk2.dim(" (`npx skills add --help` lists every agent name)"));
|
|
590
|
+
} else {
|
|
518
591
|
console.log(chalk2.bold(" AI Skills (recommended):"));
|
|
519
|
-
console.log(chalk2.dim(
|
|
592
|
+
console.log(chalk2.dim(` ${skillsInstallHint(DEFAULT_SKILLS_AGENT)}`));
|
|
593
|
+
console.log(
|
|
594
|
+
chalk2.dim(` Replace \`${DEFAULT_SKILLS_AGENT}\` with your own agent runtime if it differs.`)
|
|
595
|
+
);
|
|
520
596
|
}
|
|
521
597
|
console.log("");
|
|
522
598
|
} catch (error) {
|
package/dist/templates/AGENTS.md
CHANGED
|
@@ -73,13 +73,19 @@ Run `npm run build` when you need the compiled `dist/objectstack.json` artifact.
|
|
|
73
73
|
|
|
74
74
|
## AI Skills
|
|
75
75
|
|
|
76
|
-
This project uses ObjectStack skills from `objectstack-ai/objectstack`.
|
|
77
|
-
|
|
76
|
+
This project uses ObjectStack skills from `objectstack-ai/objectstack`. They
|
|
77
|
+
were installed for one agent runtime, live in `.claude/skills/`, and are
|
|
78
|
+
committed with the project. Install or update them with the standard
|
|
79
|
+
[skills CLI](https://skills.sh/), naming your runtime:
|
|
78
80
|
|
|
79
81
|
```bash
|
|
80
|
-
npx skills add objectstack-ai/objectstack/skills
|
|
82
|
+
npx skills add objectstack-ai/objectstack/skills --skill '*' --agent claude-code -y
|
|
81
83
|
```
|
|
82
84
|
|
|
85
|
+
Adding a second runtime? Re-run it with that runtime's `--agent` name.
|
|
86
|
+
⛔ Do not use `--all`: it writes the same bundle to `.agents/`, `agent/` and
|
|
87
|
+
`.claude/` at once, and all three then land in your next commit.
|
|
88
|
+
|
|
83
89
|
Skills are triggered automatically based on task context:
|
|
84
90
|
|
|
85
91
|
| Skill | Trigger Context |
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Continuous integration for this ObjectStack app.
|
|
2
|
+
#
|
|
3
|
+
# ObjectStack metadata fails SILENTLY at runtime, not at edit time: a mistyped
|
|
4
|
+
# permission grant or a bare field name in an action predicate raises nothing,
|
|
5
|
+
# it just behaves wrongly for whoever hits it first. `objectstack validate` is
|
|
6
|
+
# where that class surfaces — and running it is something a human, or an AI
|
|
7
|
+
# agent authoring metadata in this project, has to remember. This workflow is
|
|
8
|
+
# what makes it unskippable. AGENTS.md carries the authoring conventions it
|
|
9
|
+
# enforces.
|
|
10
|
+
#
|
|
11
|
+
# One job, one file: a starting point, not a CI framework. Grow it with the
|
|
12
|
+
# project — add a step when you add a script, a second job when you deploy.
|
|
13
|
+
|
|
14
|
+
name: CI
|
|
15
|
+
|
|
16
|
+
on: [push, pull_request]
|
|
17
|
+
|
|
18
|
+
# Read-only. Nothing here writes to the repository or publishes anything.
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
verify:
|
|
24
|
+
name: Validate
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
timeout-minutes: 15
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v7
|
|
29
|
+
|
|
30
|
+
# pnpm BEFORE setup-node, deliberately. `cache: pnpm` below makes
|
|
31
|
+
# setup-node shell out to pnpm to locate the store, so pnpm has to be on
|
|
32
|
+
# PATH by then. Reversed, this does not degrade — the job dies in the
|
|
33
|
+
# setup step with "Unable to locate executable file: pnpm".
|
|
34
|
+
#
|
|
35
|
+
# `version` is explicit because this project declares no `packageManager`
|
|
36
|
+
# field — pinning one would make the project pnpm-only, and a
|
|
37
|
+
# corepack-driven npm or yarn then refuses to run in it — so
|
|
38
|
+
# pnpm/action-setup has nothing to resolve from. Keep this in step with
|
|
39
|
+
# the `engines.pnpm` floor in package.json.
|
|
40
|
+
- uses: pnpm/action-setup@v6
|
|
41
|
+
with:
|
|
42
|
+
version: 10
|
|
43
|
+
|
|
44
|
+
- uses: actions/setup-node@v7
|
|
45
|
+
with:
|
|
46
|
+
node-version: '22'
|
|
47
|
+
cache: pnpm
|
|
48
|
+
|
|
49
|
+
# `--frozen-lockfile` installs exactly what the lockfile records and fails
|
|
50
|
+
# when the two disagree, so `pnpm-lock.yaml` has to be committed.
|
|
51
|
+
# Scaffolding wrote it for you unless you passed `--skip-install`; in that
|
|
52
|
+
# case run `pnpm install` once and commit the result.
|
|
53
|
+
- run: pnpm install --frozen-lockfile
|
|
54
|
+
|
|
55
|
+
# The gate this file exists for: schema, CEL predicates and widget
|
|
56
|
+
# bindings. The same checks `pnpm build` runs, without producing an
|
|
57
|
+
# artifact.
|
|
58
|
+
- run: pnpm validate
|
|
59
|
+
|
|
60
|
+
# Not a second spelling of `validate`. The two share the authoring-rule
|
|
61
|
+
# engine but not the rule set — the hook-body lowering check, which
|
|
62
|
+
# catches handlers that silently stop lowering to metadata-only bodies
|
|
63
|
+
# (a change of deployment shape, from a refactor that looks like
|
|
64
|
+
# tidying), runs here and nowhere else.
|
|
65
|
+
- run: pnpm lint
|
|
66
|
+
|
|
67
|
+
- run: pnpm typecheck
|
|
@@ -100,6 +100,8 @@ After editing any metadata, run:
|
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
102
|
pnpm validate # schema + CEL predicates + widget bindings (no artifact)
|
|
103
|
+
pnpm lint # authoring rules validate does not run — e.g. a handler that
|
|
104
|
+
# stopped lowering to a metadata-only body
|
|
103
105
|
pnpm typecheck # TypeScript types against @objectstack/spec
|
|
104
106
|
```
|
|
105
107
|
|
|
@@ -138,5 +140,10 @@ covered in [Self-Hosted Deployment](https://objectstack.ai/docs/deployment/self-
|
|
|
138
140
|
- Add a flow or automation: see `objectstack-automation`.
|
|
139
141
|
- Add an AI agent: see `objectstack-ai`.
|
|
140
142
|
|
|
141
|
-
Skills are
|
|
142
|
-
(see `AGENTS.md`) and also show
|
|
143
|
+
Skills live in `.claude/skills/` and are committed with this project, once.
|
|
144
|
+
They are installed one agent runtime at a time (see `AGENTS.md`), and also show
|
|
145
|
+
up in the in-IDE assistant catalog:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx skills add objectstack-ai/objectstack/skills --skill '*' --agent claude-code -y
|
|
149
|
+
```
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"start": "objectstack start",
|
|
12
12
|
"build": "objectstack build",
|
|
13
13
|
"validate": "objectstack validate",
|
|
14
|
+
"lint": "objectstack lint",
|
|
14
15
|
"typecheck": "tsc --noEmit"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
@@ -23,6 +24,6 @@
|
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"@objectstack/cli": "^17.0.0",
|
|
26
|
-
"typescript": "^
|
|
27
|
+
"typescript": "^5.3.0"
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -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.4.0",
|
|
4
4
|
"description": "Create a new ObjectStack project — npx create-objectstack",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-objectstack": "./bin/create-objectstack.js"
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"@types/node": "^26.2.0",
|
|
29
29
|
"tsup": "^8.5.1",
|
|
30
30
|
"typescript": "^6.0.3",
|
|
31
|
-
"vitest": "^4.1.10"
|
|
31
|
+
"vitest": "^4.1.10",
|
|
32
|
+
"yaml": "^2.9.0"
|
|
32
33
|
},
|
|
33
34
|
"repository": {
|
|
34
35
|
"type": "git",
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"node": ">=22.0.0"
|
|
50
51
|
},
|
|
51
52
|
"scripts": {
|
|
52
|
-
"build": "tsup",
|
|
53
|
+
"build": "node ../../scripts/sync-scaffold-emission-policy.mjs && tsup && node ../../scripts/check-dts-emitted.mjs",
|
|
53
54
|
"typecheck": "tsc --noEmit",
|
|
54
55
|
"dev": "tsup --watch",
|
|
55
56
|
"test": "vitest run"
|