From d519c52b7617d5337115097fbd5b3e27adeb8831 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 13:40:32 +0000 Subject: [PATCH] fix(create-objectstack): retire the five remote templates from the catalog (#8677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The five remote content templates (todo, compliance, content, contracts, procurement) were delisted from the official marketplace and are no longer maintained, but the scaffolder carries its own hardcoded catalog and never learned that: `--help` recommended all five by name with marketing copy, and the `Available:` line on a bad `-t` offered them too. - `blank` (bundled, offline) is now the whole catalog. - A retired name is refused by name — `-t todo` says the template was retired instead of the generic unknown-template error, which reads as a typo. - The GitHub tarball-fetch path that served them is removed with its `tar` dependency; nothing else reached it. - Catalog moved to template-registry.ts so tests can import it (importing index.ts runs the CLI), replacing the README ratchet's regex text-parse. Docs, the skills catalog, the scaffold-e2e registry-canary matrix and the QA checklist item that enumerated the five are trimmed with it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MX1qcBzfwZb5wkRrJTNbhH --- .changeset/retire-remote-template-catalog.md | 23 +++ .github/workflows/scaffold-e2e.yml | 10 +- .../getting-started/your-first-project.mdx | 17 +- content/docs/plugins/packages.mdx | 2 +- docs/qa/platform-checklist/areas/cli.json | 7 +- packages/create-objectstack/README.md | 18 +- packages/create-objectstack/package.json | 3 +- packages/create-objectstack/src/index.ts | 175 ++++-------------- .../src/template-consistency.test.ts | 16 +- .../src/template-registry.test.ts | 115 ++++++++++++ .../src/template-registry.ts | 74 ++++++++ pnpm-lock.yaml | 43 ----- skills/objectstack-platform/SKILL.md | 39 ++-- skills/objectstack-platform/evals/README.md | 2 +- 14 files changed, 299 insertions(+), 245 deletions(-) create mode 100644 .changeset/retire-remote-template-catalog.md create mode 100644 packages/create-objectstack/src/template-registry.test.ts create mode 100644 packages/create-objectstack/src/template-registry.ts diff --git a/.changeset/retire-remote-template-catalog.md b/.changeset/retire-remote-template-catalog.md new file mode 100644 index 0000000000..3037c88739 --- /dev/null +++ b/.changeset/retire-remote-template-catalog.md @@ -0,0 +1,23 @@ +--- +'create-objectstack': minor +--- + +Retire the five remote content templates from the scaffolder's catalog. + +`todo`, `compliance`, `content`, `contracts` and `procurement` were delisted +from the official ObjectStack template marketplace and are no longer +maintained, but the CLI carried its own hardcoded catalog and never learned +that: `--help` recommended all five by name with marketing descriptions, and +the `Available:` line on a bad `-t` offered them too. + +- `blank` (bundled, offline) is now the whole catalog, so the help text + advertises only what is actually supported. +- Asking for one of the five by name — `-t todo` in an old script or tutorial — + is refused with a message that says the template was retired, instead of the + generic "Unknown template" error that reads as a typo. +- The GitHub tarball-fetch path that served the remote templates is removed + along with its `tar` dependency; nothing else reached it. + +Note this corrects the catalog at HEAD only. Already-published versions keep +advertising the retired templates until a new version of `create-objectstack` +is released. diff --git a/.github/workflows/scaffold-e2e.yml b/.github/workflows/scaffold-e2e.yml index 4814828826..78956b9dc8 100644 --- a/.github/workflows/scaffold-e2e.yml +++ b/.github/workflows/scaffold-e2e.yml @@ -252,7 +252,11 @@ jobs: strategy: fail-fast: false matrix: - template: [blank, todo, compliance, content, contracts, procurement] + # `blank` is the whole catalog: the five remote content templates + # (todo, compliance, content, contracts, procurement) were delisted + # from the marketplace and retired from the scaffolder, so canarying + # them would gate the release on unmaintained content. + template: [blank] steps: - name: Setup Node.js uses: actions/setup-node@v7 @@ -264,7 +268,9 @@ jobs: cd "$RUNNER_TEMP" npx -y create-objectstack@latest canary-app -t ${{ matrix.template }} --skip-skills - # Remote templates ship `build` (same gates) but not always `validate`. + # `build` is the gate every template must pass; `validate` is run only + # when the scaffolded project defines it (the now-retired remote + # templates did not always ship it). - name: Gate the generated project run: | cd "$RUNNER_TEMP/canary-app" diff --git a/content/docs/getting-started/your-first-project.mdx b/content/docs/getting-started/your-first-project.mdx index 953e1b0066..f0c1db5fe3 100644 --- a/content/docs/getting-started/your-first-project.mdx +++ b/content/docs/getting-started/your-first-project.mdx @@ -58,20 +58,17 @@ bundle) with `--skip-skills` if you prefer — `AGENTS.md` is always written. | Template | Source | Description | |:---|:---|:---| | `blank` *(default)* | bundled, works offline | One object, REST API — a clean slate | -| `todo` | remote | Universal task & project management starter | -| `compliance` | remote | Compliance posture & evidence management (SOC2 / ISO27001) | -| `content` | remote | Content marketing pipeline — editorial calendar & channel ROI | -| `contracts` | remote | Post-signature CLM — approvals, obligations, renewals | -| `procurement` | remote | Source-to-pay — vendors, POs, receipts, invoice matching | ```bash -npm create objectstack@latest my-app -- --template todo +npm create objectstack@latest my-app -- --template blank ``` -Remote templates are fetched from the -[`objectstack-ai/templates`](https://github.com/objectstack-ai/templates) -repository at scaffold time and need network access; `blank` is bundled inside -the npm package and always works offline. +`blank` is bundled inside the npm package, so scaffolding always works offline. + +The remote content templates (`todo`, `compliance`, `content`, `contracts`, +`procurement`) have been retired: they were delisted from the ObjectStack +template marketplace and are no longer maintained. Asking for one by name says +so explicitly. `os init` (from `@objectstack/cli`) is an alternative scaffolder with `app` / diff --git a/content/docs/plugins/packages.mdx b/content/docs/plugins/packages.mdx index 7c634055bf..4ff3958578 100644 --- a/content/docs/plugins/packages.mdx +++ b/content/docs/plugins/packages.mdx @@ -434,7 +434,7 @@ npx os serve --dev **Project Scaffolding** — Create new ObjectStack projects. -- **Templates**: `blank` (default, bundled), plus remote templates `todo`, `compliance`, `content`, `contracts`, `procurement` +- **Templates**: `blank` (default, bundled, works offline) — the remote content templates (`todo`, `compliance`, `content`, `contracts`, `procurement`) are retired - **When to use**: Start a new ObjectStack project - **README**: [View README](https://github.com/objectstack-ai/objectstack/blob/main/packages/create-objectstack/README.md) diff --git a/docs/qa/platform-checklist/areas/cli.json b/docs/qa/platform-checklist/areas/cli.json index e730b92f59..d0c5277f20 100644 --- a/docs/qa/platform-checklist/areas/cli.json +++ b/docs/qa/platform-checklist/areas/cli.json @@ -360,7 +360,7 @@ "title": "The published first-run closes: create-objectstack scaffold → install → validate → build → boot → health, with the skills boundary holding", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "mixed", "personas": ["new user (published-registry consumer)"], @@ -416,7 +416,7 @@ "an internal skill appearing in a scaffolded project is the exact leak the boundary step exists for — FAIL", "a template that builds only from the repo checkout but not from the registry is the #2908 class this whole item guards" ], - "variants": ["blank", "todo", "compliance", "content", "contracts", "procurement"], + "variants": ["blank"], "traps": ["stale-dist"], "automated": { "kind": "ci", "ref": ".github/workflows/scaffold-e2e.yml" }, "source": [ @@ -424,7 +424,8 @@ "packages/create-objectstack (the scaffolder under test)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new item: the published first-run experience mirrored step-for-step from scaffold-e2e.yml, template matrix as variants, RC-window protocol refusal recorded as gate-working instead of failure", "ref": "claude/platform-test-checklist-ocwugl" } + { "revision": 1, "date": "2026-08-07", "change": "new item: the published first-run experience mirrored step-for-step from scaffold-e2e.yml, template matrix as variants, RC-window protocol refusal recorded as gate-working instead of failure", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-14", "change": "variants narrowed to [blank]: the five remote content templates were delisted from the marketplace and retired from the create-objectstack catalog, and the scaffold-e2e registry-canary matrix this item mirrors was trimmed with them", "ref": "claude/issue-8677-retire-remote-template-catalog" } ] }, { diff --git a/packages/create-objectstack/README.md b/packages/create-objectstack/README.md index 4777a45b8a..33203e71ff 100644 --- a/packages/create-objectstack/README.md +++ b/packages/create-objectstack/README.md @@ -27,16 +27,14 @@ npx create-objectstack my-app --skip-install | Template | Source | Description | | --- | --- | --- | | `blank` *(default)* | bundled (offline) | Minimal starter — one object, REST API, ready to extend | -| `todo` | remote | Universal task & project management starter | -| `compliance` | remote | Compliance posture & evidence management (SOC2 / ISO27001) | -| `content` | remote | Content marketing pipeline — editorial calendar & channel ROI | -| `contracts` | remote | Post-signature CLM — approvals, obligations, renewals | -| `procurement` | remote | Source-to-pay — vendors, POs, receipts, invoice matching | - -Remote templates are fetched from -[`objectstack-ai/templates`](https://github.com/objectstack-ai/templates) at -scaffold time and require network access; `blank` is bundled and always works -offline. + +`blank` is bundled inside the npm package, so scaffolding never needs network +access. + +The remote content templates (`todo`, `compliance`, `content`, `contracts`, +`procurement`) have been **retired** — they were delisted from the ObjectStack +template marketplace and are no longer maintained. Asking for one by name tells +you so rather than failing as an unknown template. ## Options diff --git a/packages/create-objectstack/package.json b/packages/create-objectstack/package.json index e5cf55a91d..317d96fb64 100644 --- a/packages/create-objectstack/package.json +++ b/packages/create-objectstack/package.json @@ -22,8 +22,7 @@ "license": "Apache-2.0", "dependencies": { "chalk": "^6.0.0", - "commander": "^15.0.0", - "tar": "^7.5.22" + "commander": "^15.0.0" }, "devDependencies": { "@types/node": "^26.1.2", diff --git a/packages/create-objectstack/src/index.ts b/packages/create-objectstack/src/index.ts index 48ff631a1a..fa5488234e 100644 --- a/packages/create-objectstack/src/index.ts +++ b/packages/create-objectstack/src/index.ts @@ -3,19 +3,19 @@ /** * create-objectstack — scaffold a new ObjectStack environment. * - * Two template sources: + * One template source: the bundled `blank` template. It lives at + * `dist/templates/blank/` (copied from `src/templates/blank/` by tsup + * `onSuccess`) and is cloned via recursive fs copy, which also restores the + * placeholder names npm strips at publish (see TEMPLATE_FILE_ALIASES). Always + * available offline. * - * 1. Bundled `blank` template - * Lives at `dist/templates/blank/` (copied from `src/templates/blank/` - * by tsup `onSuccess`). Cloned via recursive fs copy, which also restores - * the placeholder names npm strips at publish (see TEMPLATE_FILE_ALIASES). - * Always available offline. - * - * 2. Remote content templates (`todo`, `compliance`, `content`, - * `contracts`, `procurement`) - * Fetched as a single tarball from the sibling repo - * `objectstack-ai/templates` on GitHub, then the `packages//` - * subtree is extracted. Requires network. + * There used to be a second category — remote content templates (`todo`, + * `compliance`, `content`, `contracts`, `procurement`) fetched as a tarball + * from the sibling repo `objectstack-ai/templates`. Those five were delisted + * from the official marketplace and are no longer maintained, so the catalog + * no longer offers them and the tarball-fetch path that served them is gone. + * `template-registry.ts` still names them, so `-t todo` refuses with an + * explanation instead of a bare "unknown template". * * After the files land in `targetDir`, four files are rewritten with the * user-supplied project name: @@ -35,15 +35,8 @@ import { Command } from 'commander'; import chalk from 'chalk'; import fs from 'node:fs'; import path from 'node:path'; -import os from 'node:os'; import { execSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; -import { pipeline } from 'node:stream/promises'; -import { createGunzip } from 'node:zlib'; -import { createWriteStream, createReadStream } from 'node:fs'; -import { mkdtemp, rm } from 'node:fs/promises'; -// eslint-disable-next-line import/no-unresolved -import * as tar from 'tar'; import { syncObjectStackDeps } from './pkg-utils.js'; import { copyDir } from './template-copy.js'; @@ -52,53 +45,12 @@ import { rewriteObjectNamePrefix, findStaleNamespacePrefixes, } from './rewrite-identity.js'; +import { lookupTemplate, templateNames } from './template-registry.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const BUNDLED_TEMPLATES_DIR = path.resolve(__dirname, 'templates'); -const REMOTE_REPO = 'objectstack-ai/templates'; -const REMOTE_BRANCH = 'main'; -const REMOTE_TARBALL_URL = `https://codeload.github.com/${REMOTE_REPO}/tar.gz/refs/heads/${REMOTE_BRANCH}`; - -// ─── Template Registry ────────────────────────────────────────────── - -type TemplateSource = - | { kind: 'bundled'; dir: string } - | { kind: 'remote'; pkg: string }; - -interface TemplateInfo { - description: string; - source: TemplateSource; -} - -const TEMPLATES: Record = { - blank: { - description: 'Minimal starter — one object, REST API, ready to extend', - source: { kind: 'bundled', dir: 'blank' }, - }, - todo: { - description: 'Universal task & project management starter', - source: { kind: 'remote', pkg: 'todo' }, - }, - compliance: { - description: 'Compliance posture & evidence management (SOC2 / ISO27001)', - source: { kind: 'remote', pkg: 'compliance' }, - }, - content: { - description: 'Content marketing pipeline — editorial calendar & channel ROI', - source: { kind: 'remote', pkg: 'content' }, - }, - contracts: { - description: 'Post-signature CLM — approvals, obligations, renewals', - source: { kind: 'remote', pkg: 'contracts' }, - }, - procurement: { - description: 'Source-to-pay — vendors, POs, receipts, invoice matching', - source: { kind: 'remote', pkg: 'procurement' }, - }, -}; - // ─── Helpers ──────────────────────────────────────────────────────── function toTitleCase(str: string): string { @@ -165,72 +117,6 @@ function loadBundled(templateDir: string, targetDir: string): string[] { return collected; } -// ─── Loading: remote (GitHub tarball) ─────────────────────────────── - -async function downloadTarball(url: string, destFile: string): Promise { - const res = await fetch(url, { redirect: 'follow' }); - if (!res.ok || !res.body) { - throw new Error(`Download failed: ${url} (${res.status})`); - } - const out = createWriteStream(destFile); - // node 18+: res.body is a web ReadableStream — pipe via async iterator - // eslint-disable-next-line @typescript-eslint/no-explicit-any - for await (const chunk of res.body as any) { - out.write(chunk); - } - await new Promise((resolve, reject) => { - out.end((err: unknown) => (err ? reject(err as Error) : resolve())); - }); -} - -async function loadRemote(pkgName: string, targetDir: string): Promise { - const tmp = await mkdtemp(path.join(os.tmpdir(), 'create-objectstack-')); - try { - const tarball = path.join(tmp, 'templates.tar.gz'); - printStep(`Fetching template "${pkgName}" from ${REMOTE_REPO}@${REMOTE_BRANCH}…`); - await downloadTarball(REMOTE_TARBALL_URL, tarball); - - // GitHub tarballs nest everything under `-/`. The package we - // want lives at `-/packages//...`. We extract only - // that subtree, stripping the leading 3 path components so the contents - // of `packages//` land directly in `targetDir`. - fs.mkdirSync(targetDir, { recursive: true }); - const collected: string[] = []; - await pipeline( - createReadStream(tarball), - createGunzip(), - // eslint-disable-next-line @typescript-eslint/no-explicit-any - tar.extract({ - cwd: targetDir, - strip: 3, - filter: (p: string) => { - // p looks like: "templates-main/packages//..." - const parts = p.split('/'); - return parts[1] === 'packages' && parts[2] === pkgName && parts.length > 3; - }, - onentry: (entry: { path: string; type: string }) => { - if (entry.type === 'File') { - // entry.path is the original archive path; strip the 3 leading - // components ("templates-main/packages//") so the reported - // file matches what actually lands on disk. - const parts = entry.path.split('/').slice(3); - if (parts.length > 0) collected.push(parts.join('/')); - } - }, - } as any), - ); - if (collected.length === 0) { - throw new Error( - `Template "${pkgName}" not found in ${REMOTE_REPO}@${REMOTE_BRANCH} ` + - `(expected packages/${pkgName}/).`, - ); - } - return collected; - } finally { - await rm(tmp, { recursive: true, force: true }); - } -} - // ─── Field-aware rewrites ─────────────────────────────────────────── // // The object-name prefix walk moved to rewrite-identity.ts so it can be tested @@ -385,12 +271,16 @@ const program = new Command() .argument('[name]', 'Environment name (defaults to current directory name)') .option( '-t, --template