create-objectstack 17.1.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 +449 -0
- package/README.md +1 -1
- package/dist/chunk-ZIUW7UEA.js +112 -0
- package/dist/created-summary.d.ts +48 -0
- package/dist/created-summary.js +16 -0
- package/dist/index.js +221 -105
- package/dist/templates/AGENTS.md +1 -1
- package/dist/templates/blank/Dockerfile +2 -2
- package/dist/templates/blank/README.md +32 -8
- package/dist/templates/blank/docker-compose.yml +1 -1
- package/dist/templates/blank/objectstack.config.ts +19 -15
- package/dist/templates/blank/package.json +3 -0
- package/dist/templates/blank/pnpm-workspace.yaml +57 -2
- package/dist/templates/blank/src/objects/note.object.ts +6 -2
- package/package.json +8 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describeEntry,
|
|
3
|
+
summarizeTree
|
|
4
|
+
} from "./chunk-ZIUW7UEA.js";
|
|
5
|
+
|
|
1
6
|
// src/index.ts
|
|
2
7
|
import { Command } from "commander";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
8
|
+
import chalk2 from "chalk";
|
|
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";
|
|
6
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
|
|
7
60
|
import { fileURLToPath } from "url";
|
|
8
61
|
|
|
9
62
|
// src/pkg-utils.ts
|
|
@@ -20,41 +73,41 @@ function syncObjectStackDeps(pkg, version) {
|
|
|
20
73
|
}
|
|
21
74
|
|
|
22
75
|
// src/template-copy.ts
|
|
23
|
-
import
|
|
24
|
-
import
|
|
76
|
+
import fs2 from "fs";
|
|
77
|
+
import path2 from "path";
|
|
25
78
|
var TEMPLATE_FILE_ALIASES = /* @__PURE__ */ new Map([
|
|
26
79
|
["_gitignore", ".gitignore"]
|
|
27
80
|
]);
|
|
28
81
|
function copyDir(src, dest, collected, rel = "") {
|
|
29
|
-
|
|
30
|
-
for (const entry of
|
|
31
|
-
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);
|
|
32
85
|
const outName = entry.isFile() ? TEMPLATE_FILE_ALIASES.get(entry.name) ?? entry.name : entry.name;
|
|
33
|
-
const destPath =
|
|
86
|
+
const destPath = path2.join(dest, outName);
|
|
34
87
|
const relPath = rel ? `${rel}/${outName}` : outName;
|
|
35
88
|
if (entry.isDirectory()) {
|
|
36
89
|
copyDir(srcPath, destPath, collected, relPath);
|
|
37
90
|
} else if (entry.isFile()) {
|
|
38
|
-
|
|
91
|
+
fs2.copyFileSync(srcPath, destPath);
|
|
39
92
|
collected.push(relPath);
|
|
40
93
|
}
|
|
41
94
|
}
|
|
42
95
|
}
|
|
43
96
|
|
|
44
97
|
// src/rewrite-identity.ts
|
|
45
|
-
import
|
|
46
|
-
import
|
|
98
|
+
import fs3 from "fs";
|
|
99
|
+
import path3 from "path";
|
|
47
100
|
var CONFIG_NAMESPACE_RE = /\bnamespace:\s*(['"`])([a-z0-9_]+)\1/i;
|
|
48
101
|
function readTemplateNamespace(targetDir) {
|
|
49
|
-
const configPath =
|
|
50
|
-
if (
|
|
51
|
-
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"));
|
|
52
105
|
if (m) return m[2];
|
|
53
106
|
}
|
|
54
|
-
const manifestPath =
|
|
55
|
-
if (
|
|
107
|
+
const manifestPath = path3.join(targetDir, "objectstack.manifest.json");
|
|
108
|
+
if (fs3.existsSync(manifestPath)) {
|
|
56
109
|
try {
|
|
57
|
-
const m = JSON.parse(
|
|
110
|
+
const m = JSON.parse(fs3.readFileSync(manifestPath, "utf8"));
|
|
58
111
|
if (typeof m.namespace === "string" && m.namespace) return m.namespace;
|
|
59
112
|
} catch {
|
|
60
113
|
}
|
|
@@ -62,9 +115,9 @@ function readTemplateNamespace(targetDir) {
|
|
|
62
115
|
return void 0;
|
|
63
116
|
}
|
|
64
117
|
function tsFiles(dir, out = []) {
|
|
65
|
-
if (!
|
|
66
|
-
for (const entry of
|
|
67
|
-
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);
|
|
68
121
|
if (entry.isDirectory()) {
|
|
69
122
|
if (entry.name === "node_modules") continue;
|
|
70
123
|
tsFiles(full, out);
|
|
@@ -78,7 +131,7 @@ var namePrefixRe = (ns, flags) => new RegExp(`(\\bname:\\s*)(['"\`])${ns}_([a-z0
|
|
|
78
131
|
function rewriteObjectNamePrefix(dir, from, to) {
|
|
79
132
|
let rewritten = 0;
|
|
80
133
|
for (const file of tsFiles(dir)) {
|
|
81
|
-
const before =
|
|
134
|
+
const before = fs3.readFileSync(file, "utf8");
|
|
82
135
|
const after = before.replace(
|
|
83
136
|
namePrefixRe(from, "g"),
|
|
84
137
|
(_m, prefix, q, rest) => {
|
|
@@ -86,7 +139,7 @@ function rewriteObjectNamePrefix(dir, from, to) {
|
|
|
86
139
|
return `${prefix}${q}${to}_${rest}${q}`;
|
|
87
140
|
}
|
|
88
141
|
);
|
|
89
|
-
if (after !== before)
|
|
142
|
+
if (after !== before) fs3.writeFileSync(file, after);
|
|
90
143
|
}
|
|
91
144
|
return rewritten;
|
|
92
145
|
}
|
|
@@ -94,11 +147,11 @@ function findStaleNamespacePrefixes(dir, oldNs) {
|
|
|
94
147
|
const re = namePrefixRe(oldNs, "");
|
|
95
148
|
const stale = [];
|
|
96
149
|
for (const file of tsFiles(dir)) {
|
|
97
|
-
const lines =
|
|
150
|
+
const lines = fs3.readFileSync(file, "utf8").split("\n");
|
|
98
151
|
for (let i = 0; i < lines.length; i++) {
|
|
99
152
|
if (re.test(lines[i])) {
|
|
100
153
|
stale.push({
|
|
101
|
-
file:
|
|
154
|
+
file: path3.relative(dir, file),
|
|
102
155
|
line: i + 1,
|
|
103
156
|
text: lines[i].trim()
|
|
104
157
|
});
|
|
@@ -133,13 +186,13 @@ function templateNames() {
|
|
|
133
186
|
}
|
|
134
187
|
|
|
135
188
|
// src/runtime-image.ts
|
|
136
|
-
import
|
|
137
|
-
import
|
|
189
|
+
import fs4 from "fs";
|
|
190
|
+
import path4 from "path";
|
|
138
191
|
var RUNTIME_FROM_RE = /^FROM ghcr\.io\/objectstack-ai\/objectstack:([A-Za-z0-9_][A-Za-z0-9_.+-]*)[ \t]*$/;
|
|
139
192
|
var PINNABLE_VERSION_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
140
193
|
var PROSE_COMMENT_RE = /^#[ \t]+\S/;
|
|
141
194
|
function readResolvedCliVersion(targetDir) {
|
|
142
|
-
const pkgPath =
|
|
195
|
+
const pkgPath = path4.join(
|
|
143
196
|
targetDir,
|
|
144
197
|
"node_modules",
|
|
145
198
|
"@objectstack",
|
|
@@ -147,7 +200,7 @@ function readResolvedCliVersion(targetDir) {
|
|
|
147
200
|
"package.json"
|
|
148
201
|
);
|
|
149
202
|
try {
|
|
150
|
-
const version = JSON.parse(
|
|
203
|
+
const version = JSON.parse(fs4.readFileSync(pkgPath, "utf8")).version;
|
|
151
204
|
return typeof version === "string" && PINNABLE_VERSION_RE.test(version) ? version : void 0;
|
|
152
205
|
} catch {
|
|
153
206
|
return void 0;
|
|
@@ -165,10 +218,10 @@ function pinRuntimeImage(targetDir, version) {
|
|
|
165
218
|
if (!PINNABLE_VERSION_RE.test(version)) {
|
|
166
219
|
return { pinned: false, reason: `'${version}' is not a pinnable version` };
|
|
167
220
|
}
|
|
168
|
-
const dockerfile =
|
|
221
|
+
const dockerfile = path4.join(targetDir, "Dockerfile");
|
|
169
222
|
let text;
|
|
170
223
|
try {
|
|
171
|
-
text =
|
|
224
|
+
text = fs4.readFileSync(dockerfile, "utf8");
|
|
172
225
|
} catch {
|
|
173
226
|
return { pinned: false, reason: "no Dockerfile in the scaffolded project" };
|
|
174
227
|
}
|
|
@@ -188,14 +241,34 @@ function pinRuntimeImage(targetDir, version) {
|
|
|
188
241
|
if (!check || check[1] !== version) {
|
|
189
242
|
return { pinned: false, reason: "rewrite did not produce the pinned tag" };
|
|
190
243
|
}
|
|
191
|
-
|
|
244
|
+
fs4.writeFileSync(dockerfile, pinnedText);
|
|
192
245
|
return { pinned: true, tag: version };
|
|
193
246
|
}
|
|
194
247
|
|
|
248
|
+
// src/banner.ts
|
|
249
|
+
import chalk from "chalk";
|
|
250
|
+
var PREFIX = " \u25C6 Create ObjectStack ";
|
|
251
|
+
var MIN_INNER_WIDTH = 35;
|
|
252
|
+
var MIN_TRAILING_PAD = 3;
|
|
253
|
+
function renderVersionBanner(version) {
|
|
254
|
+
const versionLabel = `v${version}`;
|
|
255
|
+
const innerWidth = Math.max(
|
|
256
|
+
MIN_INNER_WIDTH,
|
|
257
|
+
PREFIX.length + versionLabel.length + MIN_TRAILING_PAD
|
|
258
|
+
);
|
|
259
|
+
const trailingPad = innerWidth - PREFIX.length - versionLabel.length;
|
|
260
|
+
const border = "\u2550".repeat(innerWidth);
|
|
261
|
+
return [
|
|
262
|
+
chalk.bold.cyan(` \u2554${border}\u2557`),
|
|
263
|
+
chalk.bold.cyan(" \u2551") + chalk.bold(PREFIX) + chalk.dim(versionLabel) + chalk.bold.cyan(`${" ".repeat(trailingPad)}\u2551`),
|
|
264
|
+
chalk.bold.cyan(` \u255A${border}\u255D`)
|
|
265
|
+
];
|
|
266
|
+
}
|
|
267
|
+
|
|
195
268
|
// src/index.ts
|
|
196
269
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
197
|
-
var __dirname2 =
|
|
198
|
-
var BUNDLED_TEMPLATES_DIR =
|
|
270
|
+
var __dirname2 = path5.dirname(__filename2);
|
|
271
|
+
var BUNDLED_TEMPLATES_DIR = path5.resolve(__dirname2, "templates");
|
|
199
272
|
function toTitleCase(str) {
|
|
200
273
|
return str.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
201
274
|
}
|
|
@@ -212,44 +285,36 @@ function sanitizeNamespace(name) {
|
|
|
212
285
|
}
|
|
213
286
|
function readCliVersion() {
|
|
214
287
|
try {
|
|
215
|
-
const pkgPath =
|
|
216
|
-
const pkg = JSON.parse(
|
|
288
|
+
const pkgPath = path5.resolve(__dirname2, "..", "package.json");
|
|
289
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
|
|
217
290
|
return String(pkg.version || "0.0.0");
|
|
218
291
|
} catch {
|
|
219
292
|
return "0.0.0";
|
|
220
293
|
}
|
|
221
294
|
}
|
|
222
295
|
function printHeader(title) {
|
|
223
|
-
console.log(
|
|
296
|
+
console.log(chalk2.bold(`
|
|
224
297
|
\u25C6 ${title}`));
|
|
225
|
-
console.log(
|
|
298
|
+
console.log(chalk2.dim("\u2500".repeat(40)));
|
|
226
299
|
}
|
|
227
300
|
function printKV(key, value) {
|
|
228
|
-
console.log(` ${
|
|
301
|
+
console.log(` ${chalk2.dim(key + ":")} ${chalk2.white(value)}`);
|
|
229
302
|
}
|
|
230
303
|
function printSuccess(msg) {
|
|
231
|
-
console.log(
|
|
304
|
+
console.log(chalk2.green(` \u2713 ${msg}`));
|
|
232
305
|
}
|
|
233
306
|
function printError(msg) {
|
|
234
|
-
console.log(
|
|
307
|
+
console.log(chalk2.red(` \u2717 ${msg}`));
|
|
235
308
|
}
|
|
236
309
|
function printStep(msg) {
|
|
237
|
-
console.log(
|
|
310
|
+
console.log(chalk2.yellow(` \u2192 ${msg}`));
|
|
238
311
|
}
|
|
239
312
|
function printWarning(msg) {
|
|
240
|
-
console.log(
|
|
241
|
-
}
|
|
242
|
-
function detectPackageManager() {
|
|
243
|
-
try {
|
|
244
|
-
execSync("pnpm --version", { stdio: "ignore" });
|
|
245
|
-
return "pnpm";
|
|
246
|
-
} catch {
|
|
247
|
-
return "npm";
|
|
248
|
-
}
|
|
313
|
+
console.log(chalk2.yellow(` \u26A0 ${msg}`));
|
|
249
314
|
}
|
|
250
315
|
function loadBundled(templateDir, targetDir) {
|
|
251
|
-
const src =
|
|
252
|
-
if (!
|
|
316
|
+
const src = path5.join(BUNDLED_TEMPLATES_DIR, templateDir);
|
|
317
|
+
if (!fs5.existsSync(src)) {
|
|
253
318
|
throw new Error(`Bundled template missing on disk: ${src}`);
|
|
254
319
|
}
|
|
255
320
|
const collected = [];
|
|
@@ -259,39 +324,39 @@ function loadBundled(templateDir, targetDir) {
|
|
|
259
324
|
function rewriteProjectIdentity(targetDir, projectName, namespace) {
|
|
260
325
|
const title = toTitleCase(projectName);
|
|
261
326
|
const templateNamespace = readTemplateNamespace(targetDir);
|
|
262
|
-
const pkgPath =
|
|
263
|
-
if (
|
|
327
|
+
const pkgPath = path5.join(targetDir, "package.json");
|
|
328
|
+
if (fs5.existsSync(pkgPath)) {
|
|
264
329
|
try {
|
|
265
|
-
const pkg = JSON.parse(
|
|
330
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
|
|
266
331
|
pkg.name = projectName;
|
|
267
332
|
syncObjectStackDeps(pkg, readCliVersion());
|
|
268
|
-
|
|
333
|
+
fs5.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
269
334
|
} catch {
|
|
270
335
|
}
|
|
271
336
|
}
|
|
272
|
-
const manifestPath =
|
|
273
|
-
if (
|
|
337
|
+
const manifestPath = path5.join(targetDir, "objectstack.manifest.json");
|
|
338
|
+
if (fs5.existsSync(manifestPath)) {
|
|
274
339
|
try {
|
|
275
|
-
const m = JSON.parse(
|
|
340
|
+
const m = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
|
|
276
341
|
m.name = projectName;
|
|
277
342
|
m.displayName = title;
|
|
278
343
|
if ("namespace" in m) m.namespace = namespace;
|
|
279
344
|
delete m.description;
|
|
280
|
-
|
|
345
|
+
fs5.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
|
|
281
346
|
} catch {
|
|
282
347
|
}
|
|
283
348
|
}
|
|
284
|
-
const configPath =
|
|
285
|
-
if (
|
|
286
|
-
let cfg =
|
|
349
|
+
const configPath = path5.join(targetDir, "objectstack.config.ts");
|
|
350
|
+
if (fs5.existsSync(configPath)) {
|
|
351
|
+
let cfg = fs5.readFileSync(configPath, "utf8");
|
|
287
352
|
cfg = cfg.replace(/(\bid:\s*)(['"`])[^'"`]*\2/, `$1$2${projectName}$2`);
|
|
288
353
|
cfg = cfg.replace(/(\bnamespace:\s*)(['"`])[^'"`]*\2/, `$1$2${namespace}$2`);
|
|
289
354
|
cfg = cfg.replace(/(\bname:\s*)(['"`])[^'"`]*\2/, `$1$2${title}$2`);
|
|
290
355
|
cfg = cfg.replace(/^[ \t]*description:\s*(['"`])[^'"`]*\1,?\r?\n/m, "");
|
|
291
|
-
|
|
356
|
+
fs5.writeFileSync(configPath, cfg);
|
|
292
357
|
}
|
|
293
358
|
if (templateNamespace && namespace !== templateNamespace) {
|
|
294
|
-
const srcDir =
|
|
359
|
+
const srcDir = path5.join(targetDir, "src");
|
|
295
360
|
rewriteObjectNamePrefix(srcDir, templateNamespace, namespace);
|
|
296
361
|
const stale = findStaleNamespacePrefixes(srcDir, templateNamespace);
|
|
297
362
|
if (stale.length > 0) {
|
|
@@ -305,32 +370,75 @@ The generated project would fail 'objectstack build' on the \${namespace}_\${sho
|
|
|
305
370
|
);
|
|
306
371
|
}
|
|
307
372
|
}
|
|
308
|
-
const readmePath =
|
|
309
|
-
if (
|
|
310
|
-
let md =
|
|
373
|
+
const readmePath = path5.join(targetDir, "README.md");
|
|
374
|
+
if (fs5.existsSync(readmePath)) {
|
|
375
|
+
let md = fs5.readFileSync(readmePath, "utf8");
|
|
311
376
|
md = md.replace(/^#\s+.*$/m, `# ${title}`);
|
|
312
|
-
|
|
377
|
+
fs5.writeFileSync(readmePath, md);
|
|
313
378
|
}
|
|
314
379
|
writeAgentGuides(targetDir, title, projectName);
|
|
315
380
|
}
|
|
316
381
|
function writeAgentGuides(targetDir, title, projectName) {
|
|
317
|
-
const templatePath =
|
|
382
|
+
const templatePath = path5.join(BUNDLED_TEMPLATES_DIR, "AGENTS.md");
|
|
318
383
|
let template;
|
|
319
384
|
try {
|
|
320
|
-
template =
|
|
385
|
+
template = fs5.readFileSync(templatePath, "utf8");
|
|
321
386
|
} catch (err) {
|
|
322
387
|
if (err?.code === "ENOENT") return;
|
|
323
388
|
throw err;
|
|
324
389
|
}
|
|
325
390
|
const rendered = template.replace(/\{\{PROJECT_TITLE\}\}/g, title).replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
326
|
-
writeIfAbsent(
|
|
327
|
-
const copilotPath =
|
|
328
|
-
|
|
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 });
|
|
329
394
|
writeIfAbsent(copilotPath, rendered);
|
|
330
395
|
}
|
|
396
|
+
function topLevelNames(dir) {
|
|
397
|
+
try {
|
|
398
|
+
return new Set(fs5.readdirSync(dir));
|
|
399
|
+
} catch {
|
|
400
|
+
return /* @__PURE__ */ new Set();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function printCreatedSummary(targetDir, opts) {
|
|
404
|
+
const entries = summarizeTree(targetDir);
|
|
405
|
+
if (entries.length === 0) return;
|
|
406
|
+
console.log(
|
|
407
|
+
chalk2.bold(opts.wasEmpty ? " Created files:" : " Project contents:")
|
|
408
|
+
);
|
|
409
|
+
if (!opts.wasEmpty) {
|
|
410
|
+
console.log(
|
|
411
|
+
chalk2.dim(" (the directory already had contents; this lists all of it)")
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
const isSkillPath = (p) => opts.skillPaths.has(p.split("/")[0]);
|
|
415
|
+
const width = Math.min(
|
|
416
|
+
44,
|
|
417
|
+
Math.max(...entries.map((e) => e.path.length)) + 2
|
|
418
|
+
);
|
|
419
|
+
let flagged = false;
|
|
420
|
+
for (const entry of entries) {
|
|
421
|
+
const note = describeEntry(entry);
|
|
422
|
+
const flag = isSkillPath(entry.path);
|
|
423
|
+
if (flag) flagged = true;
|
|
424
|
+
const pad = note || flag ? entry.path.padEnd(width) : entry.path;
|
|
425
|
+
const line = ` + ${pad}${note ? chalk2.dim(note) : ""}`;
|
|
426
|
+
console.log(chalk2.green(line) + (flag ? chalk2.yellow(" \u26A0 skills") : ""));
|
|
427
|
+
}
|
|
428
|
+
if (flagged) {
|
|
429
|
+
console.log("");
|
|
430
|
+
console.log(
|
|
431
|
+
chalk2.yellow(" \u26A0 Skill files run with your coding agent's full permissions.")
|
|
432
|
+
);
|
|
433
|
+
console.log(
|
|
434
|
+
chalk2.dim(" Review the paths marked \u26A0 above before letting an agent use them.")
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
console.log("");
|
|
438
|
+
}
|
|
331
439
|
function writeIfAbsent(filePath, contents) {
|
|
332
440
|
try {
|
|
333
|
-
|
|
441
|
+
fs5.writeFileSync(filePath, contents, { flag: "wx" });
|
|
334
442
|
} catch (err) {
|
|
335
443
|
if (err?.code !== "EEXIST") throw err;
|
|
336
444
|
}
|
|
@@ -341,65 +449,65 @@ var program = new Command().name("create-objectstack").description("Create a new
|
|
|
341
449
|
"blank"
|
|
342
450
|
).option("--skip-install", "Skip dependency installation").option("--skip-skills", "Skip installing ObjectStack AI skills").action((name, options) => {
|
|
343
451
|
console.log("");
|
|
344
|
-
console.log(
|
|
345
|
-
console.log(chalk.bold.cyan(" \u2551") + chalk.bold(" \u25C6 Create ObjectStack ") + chalk.dim("v6.x") + chalk.bold.cyan(" \u2551"));
|
|
346
|
-
console.log(chalk.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
452
|
+
for (const line of renderVersionBanner(readCliVersion())) console.log(line);
|
|
347
453
|
printHeader("New Environment");
|
|
348
454
|
const lookup = lookupTemplate(options.template);
|
|
349
455
|
if (lookup.kind !== "found") {
|
|
350
456
|
if (lookup.kind === "retired") {
|
|
351
457
|
printError(`Template "${lookup.name}" has been retired and is no longer available.`);
|
|
352
458
|
console.log(
|
|
353
|
-
|
|
459
|
+
chalk2.dim(
|
|
354
460
|
" It was delisted from the ObjectStack template marketplace and is no longer maintained."
|
|
355
461
|
)
|
|
356
462
|
);
|
|
357
463
|
} else {
|
|
358
464
|
printError(`Unknown template: ${lookup.name}`);
|
|
359
465
|
}
|
|
360
|
-
console.log(
|
|
466
|
+
console.log(chalk2.dim(` Available: ${templateNames().join(", ")}`));
|
|
361
467
|
process.exit(1);
|
|
362
468
|
}
|
|
363
469
|
const template = lookup.template;
|
|
364
470
|
const cwd = process.cwd();
|
|
365
|
-
const projectName = name ||
|
|
471
|
+
const projectName = name || path5.basename(cwd);
|
|
366
472
|
const namespace = sanitizeNamespace(projectName);
|
|
367
|
-
const targetDir = name ?
|
|
473
|
+
const targetDir = name ? path5.resolve(cwd, name) : cwd;
|
|
368
474
|
const isCurrentDir = targetDir === cwd;
|
|
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
|
+
}
|
|
369
483
|
printKV("Environment", projectName);
|
|
370
484
|
printKV("Namespace", namespace);
|
|
371
485
|
printKV("Template", `${options.template} \u2014 ${template.description}`);
|
|
372
486
|
printKV("Directory", targetDir);
|
|
373
487
|
console.log("");
|
|
374
|
-
if (!isCurrentDir &&
|
|
375
|
-
const existing =
|
|
488
|
+
if (!isCurrentDir && fs5.existsSync(targetDir)) {
|
|
489
|
+
const existing = fs5.readdirSync(targetDir);
|
|
376
490
|
if (existing.length > 0) {
|
|
377
491
|
printError(`Directory already exists and is not empty: ${targetDir}`);
|
|
378
492
|
process.exit(1);
|
|
379
493
|
}
|
|
380
494
|
}
|
|
495
|
+
const targetWasEmpty = topLevelNames(targetDir).size === 0;
|
|
381
496
|
try {
|
|
382
|
-
|
|
497
|
+
fs5.mkdirSync(targetDir, { recursive: true });
|
|
383
498
|
const createdFiles = loadBundled(template.source.dir, targetDir);
|
|
384
499
|
rewriteProjectIdentity(targetDir, projectName, namespace);
|
|
385
|
-
|
|
386
|
-
for (const f of createdFiles.slice(0, 20)) {
|
|
387
|
-
console.log(chalk.green(` + ${f}`));
|
|
388
|
-
}
|
|
389
|
-
if (createdFiles.length > 20) {
|
|
390
|
-
console.log(chalk.dim(` \u2026 and ${createdFiles.length - 20} more`));
|
|
391
|
-
}
|
|
500
|
+
printSuccess(`Template files written (${createdFiles.length})`);
|
|
392
501
|
console.log("");
|
|
393
502
|
if (!options.skipInstall) {
|
|
394
503
|
printStep("Installing dependencies...");
|
|
395
504
|
let installed = false;
|
|
396
505
|
try {
|
|
397
|
-
|
|
398
|
-
execSync(`${pm} install`, { stdio: "inherit", cwd: targetDir });
|
|
506
|
+
execSync2(`${pm} install`, { stdio: "inherit", cwd: targetDir });
|
|
399
507
|
installed = true;
|
|
400
508
|
console.log("");
|
|
401
509
|
} catch {
|
|
402
|
-
printWarning(
|
|
510
|
+
printWarning(`Dependency installation failed. Run \`${pm} install\` manually.`);
|
|
403
511
|
console.log("");
|
|
404
512
|
}
|
|
405
513
|
if (installed) {
|
|
@@ -417,10 +525,11 @@ var program = new Command().name("create-objectstack").description("Create a new
|
|
|
417
525
|
}
|
|
418
526
|
}
|
|
419
527
|
}
|
|
528
|
+
const beforeSkills = topLevelNames(targetDir);
|
|
420
529
|
if (!options.skipInstall && !options.skipSkills) {
|
|
421
530
|
printStep("Installing AI skills for your coding agent...");
|
|
422
531
|
try {
|
|
423
|
-
|
|
532
|
+
execSync2("npx -y skills add objectstack-ai/objectstack/skills --all", {
|
|
424
533
|
stdio: "inherit",
|
|
425
534
|
cwd: targetDir
|
|
426
535
|
});
|
|
@@ -432,22 +541,29 @@ var program = new Command().name("create-objectstack").description("Create a new
|
|
|
432
541
|
console.log("");
|
|
433
542
|
}
|
|
434
543
|
}
|
|
544
|
+
const skillPaths = new Set(
|
|
545
|
+
[...topLevelNames(targetDir)].filter((p) => !beforeSkills.has(p))
|
|
546
|
+
);
|
|
547
|
+
printCreatedSummary(targetDir, { wasEmpty: targetWasEmpty, skillPaths });
|
|
435
548
|
printSuccess("Environment created!");
|
|
436
549
|
console.log("");
|
|
437
|
-
console.log(
|
|
550
|
+
console.log(chalk2.bold(" Next steps:"));
|
|
438
551
|
if (!isCurrentDir) {
|
|
439
|
-
console.log(
|
|
552
|
+
console.log(chalk2.dim(` cd ${name}`));
|
|
440
553
|
}
|
|
441
554
|
if (options.skipInstall) {
|
|
442
|
-
console.log(
|
|
555
|
+
console.log(chalk2.dim(` ${pm} install`));
|
|
443
556
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
557
|
+
const devLabel = `${pm} run dev`;
|
|
558
|
+
const validateLabel = `${pm} run validate`;
|
|
559
|
+
const labelWidth = Math.max(devLabel.length, validateLabel.length) + 3;
|
|
560
|
+
console.log(chalk2.dim(` ${devLabel.padEnd(labelWidth)}# Start development server`));
|
|
561
|
+
console.log(chalk2.dim(` ${validateLabel.padEnd(labelWidth)}# Verify metadata: schema + predicates + bindings`));
|
|
562
|
+
console.log(chalk2.dim(` ${" ".repeat(labelWidth)}# (run after every metadata edit \u2014 see AGENTS.md)`));
|
|
447
563
|
if (options.skipInstall || options.skipSkills) {
|
|
448
564
|
console.log("");
|
|
449
|
-
console.log(
|
|
450
|
-
console.log(
|
|
565
|
+
console.log(chalk2.bold(" AI Skills (recommended):"));
|
|
566
|
+
console.log(chalk2.dim(" npx skills add objectstack-ai/objectstack/skills"));
|
|
451
567
|
}
|
|
452
568
|
console.log("");
|
|
453
569
|
} catch (error) {
|
package/dist/templates/AGENTS.md
CHANGED
|
@@ -96,6 +96,6 @@ Skills are triggered automatically based on task context:
|
|
|
96
96
|
|
|
97
97
|
## Learn More
|
|
98
98
|
|
|
99
|
-
- [ObjectStack Documentation](https://objectstack.
|
|
99
|
+
- [ObjectStack Documentation](https://objectstack.ai/docs)
|
|
100
100
|
- [GitHub: objectstack-ai/objectstack](https://github.com/objectstack-ai/objectstack)
|
|
101
101
|
- [Skills CLI](https://skills.sh/) — Manage AI skills across agents
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# my-app
|
|
8
8
|
#
|
|
9
9
|
# Or run the full app + Postgres stack: see docker-compose.yml.
|
|
10
|
-
# Docs: https://
|
|
10
|
+
# Docs: https://objectstack.ai/docs/deployment/self-hosting
|
|
11
11
|
|
|
12
12
|
# ── Build stage: compile TypeScript metadata to the artifact ─────────
|
|
13
13
|
FROM node:22-slim AS build
|
|
@@ -20,7 +20,7 @@ RUN npx os build # → dist/objectstack.json
|
|
|
20
20
|
# ── Runtime: the official ObjectStack runtime image ──────────────────
|
|
21
21
|
# Ships Node + @objectstack/cli with `os start`, a non-root user, the
|
|
22
22
|
# /api/v1/health HEALTHCHECK, and OS_ARTIFACT_PATH/OS_PORT preset (port 8080)
|
|
23
|
-
# — see
|
|
23
|
+
# — see the self-hosting guide linked above.
|
|
24
24
|
#
|
|
25
25
|
# Dependencies were not installed while scaffolding, so the tag below could
|
|
26
26
|
# not be resolved for you. `latest` floats to whatever release is newest,
|