@tinycld/bootstrap 1.0.1 → 1.1.1

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.
@@ -1,65 +1,174 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync, writeFileSync } from 'node:fs';
3
- import { join } from 'node:path';
2
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join, relative } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
  // All feature members that exist in the ecosystem. The workspace manifest lists
5
6
  // ALL of them (npm tolerates absent dirs), so a partial checkout still installs.
6
7
  // But --tooling CLONES only app+core+requested — we do NOT force-clone all.
7
8
  const ALL_FEATURES = ['contacts', 'mail', 'calendar', 'drive', 'calc', 'text', 'google-takeout-import'];
8
- // Always cloned by tooling mode (the minimum viable workspace). package-scripts
9
- // ships inside the workspace meta-repo itself, so it is never cloned.
10
- const ALWAYS_CLONE = ['app', 'core'];
11
- // Listed in the manifest workspaces array (everything that could exist).
12
- const ALL_MEMBERS = ['app', 'core', 'package-scripts', ...ALL_FEATURES];
9
+ // app + core are always cloned by tooling mode (the minimum viable workspace);
10
+ // the clone set in bootstrapTooling seeds them directly (pinnable via
11
+ // appRef/coreRef). package-scripts (the tinycld-pkg CLI) now lives INSIDE the
12
+ // app member at app/package-scripts, so it arrives with the app clone and is
13
+ // never cloned separately.
14
+ // Listed in the manifest workspaces array (everything that could exist). The
15
+ // nested member path app/package-scripts is a valid npm workspace entry.
16
+ const ALL_MEMBERS = ['app', 'app/package-scripts', 'core', ...ALL_FEATURES];
13
17
  /**
14
- * Write a workspace-root package.json + .npmrc into `dir`. The workspaces array
15
- * ALWAYS lists every possible member — npm ignores entries whose dirs are
16
- * absent, so a partial checkout (app+core+one feature) installs fine, and
17
- * cloning more features later needs no manifest edit.
18
+ * Write (or merge into) a workspace-root package.json + .npmrc in `dir`.
19
+ *
20
+ * When `dir/package.json` already exists (e.g. provided by the workspace clone),
21
+ * we preserve its fields and only enforce the two things bootstrap owns:
22
+ * the complete `workspaces` list (so later --with clones need no manifest edit)
23
+ * and the `postinstall` script. All other fields — devDependencies, engines,
24
+ * volta, extra scripts, etc. — survive as-is.
25
+ *
26
+ * When no package.json exists yet (workspace clone was skipped), the full
27
+ * generated defaults are written, matching the previous behaviour.
28
+ *
29
+ * The workspaces array ALWAYS lists every possible member — npm ignores entries
30
+ * whose dirs are absent, so a partial checkout installs fine.
31
+ *
32
+ * .npmrc is written only when one does not already exist, for the same reason.
18
33
  */
19
34
  export function writeWorkspaceManifest(dir) {
35
+ const pkgPath = join(dir, 'package.json');
36
+ let existing = {};
37
+ if (existsSync(pkgPath)) {
38
+ try {
39
+ existing = JSON.parse(readFileSync(pkgPath, 'utf-8'));
40
+ }
41
+ catch {
42
+ // Unparseable existing file — start from defaults
43
+ }
44
+ }
45
+ const existingScripts = typeof existing.scripts === 'object' && existing.scripts !== null
46
+ ? existing.scripts
47
+ : {};
20
48
  const pkg = {
21
49
  name: '@tinycld/workspace',
22
50
  version: '0.0.0',
23
51
  private: true,
24
52
  type: 'module',
53
+ ...existing,
54
+ // Always enforce the canonical workspaces list and postinstall script.
25
55
  workspaces: [...ALL_MEMBERS],
26
56
  scripts: {
57
+ ...existingScripts,
27
58
  postinstall: 'cd app && npm run packages:generate && npm run assets:copy-pdfjs',
28
59
  },
29
60
  };
30
- writeFileSync(join(dir, 'package.json'), `${JSON.stringify(pkg, null, 4)}\n`);
31
- writeFileSync(join(dir, '.npmrc'), 'legacy-peer-deps=true\n');
61
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 4)}\n`);
62
+ const npmrcPath = join(dir, '.npmrc');
63
+ if (!existsSync(npmrcPath)) {
64
+ writeFileSync(npmrcPath, 'legacy-peer-deps=true\n');
65
+ }
66
+ }
67
+ /**
68
+ * Resolve the workspace-template dir relative to this module. Published builds
69
+ * have `dist/bootstrap-tooling.js` next to `templates/`; dev has
70
+ * `src/bootstrap-tooling.ts` under the same parent. Both → `../templates/workspace`.
71
+ */
72
+ function resolveWorkspaceTemplateDir() {
73
+ const here = dirname(fileURLToPath(import.meta.url));
74
+ return join(here, '..', 'templates', 'workspace');
75
+ }
76
+ /**
77
+ * Copy the workspace-root scaffolding (tinycld.packages.ts, vitest.config.ts,
78
+ * tests/ stubs) from bootstrap's templates/workspace/ into `dir`. These files
79
+ * are pure scaffolding the workspace ROOT needs but that no longer lives in a
80
+ * committed workspace repo — bootstrap is their source of truth.
81
+ *
82
+ * Never overwrites an existing file: a real workspace checkout, or a CI lane
83
+ * that supplied its own copy, keeps what it has (same non-destructive ethos as
84
+ * the member clones). Returns the relative paths actually written.
85
+ */
86
+ export function copyWorkspaceTemplate(dir, templateDir = resolveWorkspaceTemplateDir()) {
87
+ if (!existsSync(templateDir))
88
+ return [];
89
+ const written = [];
90
+ const walk = (src) => {
91
+ for (const entry of readdirSync(src)) {
92
+ const srcPath = join(src, entry);
93
+ const rel = relative(templateDir, srcPath);
94
+ const dstPath = join(dir, rel);
95
+ if (statSync(srcPath).isDirectory()) {
96
+ walk(srcPath);
97
+ continue;
98
+ }
99
+ if (existsSync(dstPath))
100
+ continue; // never overwrite
101
+ mkdirSync(dirname(dstPath), { recursive: true });
102
+ cpSync(srcPath, dstPath);
103
+ written.push(rel);
104
+ }
105
+ };
106
+ walk(templateDir);
107
+ return written;
32
108
  }
33
- function realClone(url, dest) {
34
- const r = spawnSync('git', ['clone', '--depth', '1', url, dest], { stdio: 'inherit' });
109
+ /** Split a member spec `name@ref` into its parts. No `@` → no ref (clone HEAD). */
110
+ function splitRef(spec) {
111
+ const at = spec.indexOf('@');
112
+ if (at === -1)
113
+ return { name: spec };
114
+ return { name: spec.slice(0, at), ref: spec.slice(at + 1) || undefined };
115
+ }
116
+ function realClone(url, dest, ref) {
117
+ const args = ['clone', '--depth', '1'];
118
+ if (ref)
119
+ args.push('--branch', ref);
120
+ args.push(url, dest);
121
+ const r = spawnSync('git', args, { stdio: 'inherit' });
35
122
  return r.status === 0;
36
123
  }
37
124
  /**
38
- * Assemble a workspace skeleton at opts.root: write the (full) manifest, then
39
- * clone ONLY app + core + the explicitly-requested feature members (skipping any
40
- * already present — e.g. a CI-checked-out member). Unknown member names throw.
41
- * Returns the members that ended up present. Does NOT run npm install (the
42
- * caller / CI controls that, since it may want a clean `npm ci`).
125
+ * Assemble a workspace skeleton at opts.root: write the canonical root manifest
126
+ * (writeWorkspaceManifest) + lay down the root scaffolding (copyWorkspaceTemplate:
127
+ * tinycld.packages.ts, vitest.config.ts, tests/ stubs, version files), then clone
128
+ * ONLY app + core + the explicitly-requested feature members (skipping any
129
+ * already present — e.g. a CI-checked-out member). There is NO workspace
130
+ * meta-repo clone: bootstrap is the source of all root scaffolding.
131
+ * Unknown member names throw. Returns the members that ended up present. Does
132
+ * NOT run npm install (the caller / CI controls that, since it may want a
133
+ * clean `npm ci`).
43
134
  */
44
135
  export function bootstrapTooling(opts) {
45
- const requested = opts.members ?? [];
46
- const unknown = requested.filter((m) => !ALL_FEATURES.includes(m));
136
+ // Each requested member may be `name` or `name@ref`. Validate the NAME part.
137
+ const requested = (opts.members ?? []).map(splitRef);
138
+ const unknown = requested.filter((m) => !ALL_FEATURES.includes(m.name));
47
139
  if (unknown.length > 0) {
48
- throw new Error(`Unknown feature member(s): ${unknown.join(', ')}. Known: ${ALL_FEATURES.join(', ')}`);
140
+ throw new Error(`Unknown feature member(s): ${unknown.map((m) => m.name).join(', ')}. Known: ${ALL_FEATURES.join(', ')}`);
49
141
  }
50
142
  const repoBase = opts.repoBase ?? 'git@github.com:tinycld';
51
143
  const clone = opts.clone ?? realClone;
52
- writeWorkspaceManifest(opts.root);
53
- const toClone = Array.from(new Set([...ALWAYS_CLONE, ...requested]));
54
144
  const present = [];
55
- for (const m of toClone) {
56
- const dest = join(opts.root, m);
145
+ // Write the canonical root manifest (workspaces list + postinstall), then
146
+ // lay down the root scaffolding bootstrap owns (tinycld.packages.ts,
147
+ // vitest.config.ts, tests/ stubs, .node-version, .go-version). Both are
148
+ // non-destructive: anything already present (a real workspace checkout, or
149
+ // a CI lane that supplied its own) is left as-is.
150
+ writeWorkspaceManifest(opts.root);
151
+ copyWorkspaceTemplate(opts.root);
152
+ // Build the clone set keyed by member NAME so a member passed both bare and
153
+ // with an @ref dedupes to one clone (the ref-bearing entry wins). app+core
154
+ // are always cloned and pinnable via appRef/coreRef.
155
+ const refByName = new Map();
156
+ refByName.set('app', opts.appRef);
157
+ refByName.set('core', opts.coreRef);
158
+ for (const { name, ref } of requested) {
159
+ // A later ref overrides an earlier bare entry; a bare entry never clears
160
+ // an existing ref.
161
+ if (ref !== undefined || !refByName.has(name))
162
+ refByName.set(name, ref);
163
+ }
164
+ for (const [name, ref] of refByName) {
165
+ const dest = join(opts.root, name);
57
166
  if (existsSync(join(dest, '.git')) || existsSync(join(dest, 'package.json'))) {
58
- present.push(m);
167
+ present.push(name);
59
168
  continue;
60
169
  }
61
- if (clone(`${repoBase}/${m}.git`, dest))
62
- present.push(m);
170
+ if (clone(`${repoBase}/${name}.git`, dest, ref))
171
+ present.push(name);
63
172
  }
64
173
  return present;
65
174
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap-tooling.js","sourceRoot":"","sources":["../src/bootstrap-tooling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,gFAAgF;AAChF,iFAAiF;AACjF,4EAA4E;AAC5E,MAAM,YAAY,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,uBAAuB,CAAU,CAAA;AAEhH,gFAAgF;AAChF,sEAAsE;AACtE,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,CAAU,CAAA;AAE7C,yEAAyE;AACzE,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,GAAG,YAAY,CAAU,CAAA;AAEhF;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAC9C,MAAM,GAAG,GAAG;QACR,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,OAAO;QAChB,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,CAAC,GAAG,WAAW,CAAC;QAC5B,OAAO,EAAE;YACL,WAAW,EAAE,kEAAkE;SAClF;KACJ,CAAA;IACD,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;IAC7E,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,yBAAyB,CAAC,CAAA;AACjE,CAAC;AAgBD,SAAS,SAAS,CAAC,GAAW,EAAE,IAAY;IACxC,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;IACtF,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAA;AACzB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAA6B;IAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAA;IACpC,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAkC,CAAC,CAAC,CAAA;IACnG,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,8BAA8B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC1G,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,wBAAwB,CAAA;IAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,SAAS,CAAA;IAErC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEjC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;IACpE,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAC/B,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;YAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACf,SAAQ;QACZ,CAAC;QACD,IAAI,KAAK,CAAC,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5D,CAAC;IACD,OAAO,OAAO,CAAA;AAClB,CAAC"}
1
+ {"version":3,"file":"bootstrap-tooling.js","sourceRoot":"","sources":["../src/bootstrap-tooling.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync } from 'node:fs';
3
- import { dirname, relative } from 'node:path';
3
+ import { dirname, join, relative } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
5
  import { intro, outro } from '@clack/prompts';
6
6
  import pc from 'picocolors';
@@ -12,12 +12,34 @@ import { offerLinkPackage } from "./link-package.js";
12
12
  import { runPrompts } from "./prompts.js";
13
13
  export function runToolingMode(opts) {
14
14
  const root = opts.root ?? process.cwd();
15
+ // app + core are always-cloned members, not features, so they can't ride the
16
+ // `members` list through bootstrapTooling's feature validation. Accept them
17
+ // in the SAME `--with name@ref` surface for a uniform pinned invocation
18
+ // (`--with app@v1 --with core@v2 --with mail@v3`) by peeling app@ref/core@ref
19
+ // out of `members` into appRef/coreRef. An explicit opts.appRef/coreRef wins.
20
+ let appRef = opts.appRef;
21
+ let coreRef = opts.coreRef;
22
+ const features = [];
23
+ for (const spec of opts.members ?? []) {
24
+ const at = spec.indexOf('@');
25
+ const name = at === -1 ? spec : spec.slice(0, at);
26
+ const ref = at === -1 ? undefined : spec.slice(at + 1) || undefined;
27
+ if (name === 'app')
28
+ appRef = appRef ?? ref;
29
+ else if (name === 'core')
30
+ coreRef = coreRef ?? ref;
31
+ else
32
+ features.push(spec);
33
+ }
15
34
  // Honor TINYCLD_REPO_BASE (CI sets it to an HTTPS base since runners have no
16
35
  // SSH key). Listed first so an explicit opts.repoBase still wins via spread;
17
36
  // when neither is set, bootstrapTooling falls back to its SSH default.
18
37
  const present = bootstrapTooling({
19
38
  repoBase: process.env.TINYCLD_REPO_BASE,
20
39
  ...opts,
40
+ members: features,
41
+ appRef,
42
+ coreRef,
21
43
  root,
22
44
  });
23
45
  // Mandatory members must clone — a missing app/core means a broken workspace.
@@ -51,31 +73,33 @@ async function main() {
51
73
  }
52
74
  const answers = await runPrompts(args);
53
75
  copyTemplate(resolveTemplatesRoot(import.meta.url), answers);
76
+ // The package lives at `<workspaceDir>/<slug>`, so its parent is the
77
+ // workspace dir in both attach (cwd) and bootstrap (wrapper) modes.
54
78
  const linked = await offerLinkPackage({
55
- packageName: answers.slug,
56
- targetDir: answers.targetDir,
79
+ slug: answers.slug,
80
+ workspaceDir: dirname(answers.targetDir),
57
81
  mode: resolveLinkMode(args),
58
82
  });
59
83
  const relTarget = relative(process.cwd(), answers.targetDir) || answers.targetDir;
60
84
  outro(pc.green(`Scaffolded ${pc.bold(answers.slug)} at ${pc.bold(relTarget)}`));
61
85
  // Re-derive the layout from the resolved target so the next-steps output
62
86
  // matches whatever path the user actually ended up at (default detection,
63
- // explicit --target, or interactive override). The wrapper's parent dir
64
- // names where `cd ../tinycld` lands.
87
+ // explicit --target, or interactive override). It decides whether to print
88
+ // the bootstrap "self-contained workspace" note.
65
89
  const layout = detectLayoutFromTarget(answers.targetDir, answers.slug);
66
90
  printNextSteps({ slug: answers.slug, relTarget, linked, layout });
67
91
  }
68
92
  function detectLayoutFromTarget(targetDir, slug) {
69
- // If cwd has a tinycld/ child (workspace mode) and target is a sibling of
70
- // that tinycld, we're in attach mode. Otherwise the scaffolder created a
71
- // wrapper under cwd and dropped tinycld inside it (bootstrap mode).
93
+ // If cwd is itself a workspace root and target is a direct child of it,
94
+ // we're in attach mode. Otherwise the scaffolder created a wrapper under
95
+ // cwd and the workspace meta-repo gets cloned inside it (bootstrap mode).
72
96
  const detected = detectLayout(slug);
73
97
  if (detected.mode === 'attach' && targetDir === detected.targetDir)
74
98
  return 'attach';
75
99
  if (detected.mode === 'bootstrap' && targetDir === detected.targetDir)
76
100
  return 'bootstrap';
77
101
  // User passed an explicit --target. Infer from the on-disk shape: if the
78
- // parent of targetDir has a `tinycld` child, treat as attach.
102
+ // parent of targetDir is itself a workspace root, treat as attach.
79
103
  const parent = dirname(targetDir);
80
104
  const looksAttached = detectLayout(slug, parent).mode === 'attach';
81
105
  return looksAttached ? 'attach' : 'bootstrap';
@@ -92,11 +116,15 @@ function resolveLinkMode(args) {
92
116
  function printNextSteps({ slug, relTarget, linked, layout }) {
93
117
  const lines = ['', pc.bold('Next steps:'), ''];
94
118
  let step = 1;
119
+ // The workspace root, relative to cwd. In bootstrap mode the package sits at
120
+ // `<wrapper>/<slug>`, so the wrapper (`dirname(relTarget)`) is the workspace
121
+ // root. In attach mode cwd already IS the workspace root, so it's `.`.
122
+ const wsRoot = dirname(relTarget) || '.';
95
123
  if (layout === 'bootstrap') {
96
- // Highlight that the scaffolder also dropped a tinycld checkout
97
- // alongside the new package. New users may not realize they just
98
- // got a self-contained workspace.
99
- lines.push(pc.dim(` Scaffolded a self-contained workspace alongside the package.\n The tinycld app shell lives at ./${dirname(relTarget)}/tinycld/.\n`));
124
+ // The meta-repo was cloned into the wrapper, so the wrapper itself is the
125
+ // workspace root and the new package is a member directory inside it.
126
+ // New users may not realize they just got a self-contained workspace.
127
+ lines.push(pc.dim(` Scaffolded a self-contained tinycld workspace.\n The workspace root is ./${wsRoot}/ and your package is ./${relTarget}/.\n The app member (Expo + PocketBase) is ./${wsRoot}/app/.\n`));
100
128
  }
101
129
  lines.push(` ${pc.dim(`# ${step++}. Initialize git and push to GitHub`)}`);
102
130
  lines.push(` cd ${relTarget}`);
@@ -106,18 +134,24 @@ function printNextSteps({ slug, relTarget, linked, layout }) {
106
134
  lines.push(` gh repo create tinycld/${slug} --public --source=. --push`);
107
135
  lines.push('');
108
136
  if (!linked) {
109
- lines.push(` ${pc.dim(`# ${step++}. Link into the tinycld app shell`)}`);
110
- lines.push(' cd ../tinycld');
111
- lines.push(` npm run packages:link ../${slug}`);
137
+ // Linking is now a workspace-root `npm install`: the package dir already
138
+ // exists; add it to the root package.json `workspaces` if it isn't a
139
+ // member yet, then install — npm symlinks it and the postinstall runs
140
+ // the generator.
141
+ lines.push(` ${pc.dim(`# ${step++}. Link into the workspace (add as a member, then install)`)}`);
142
+ if (wsRoot !== '.')
143
+ lines.push(` cd ${wsRoot}`);
144
+ lines.push(` # ensure "${slug}" is in this package.json's "workspaces" array, then:`);
145
+ lines.push(' npm install');
112
146
  lines.push('');
113
147
  }
114
- lines.push(` ${pc.dim(`# ${step++}. Verify (biome + tsc run from the app shell and cover the linked package)`)}`);
115
- if (linked)
116
- lines.push(' cd ../tinycld');
117
- lines.push(' npm run checks');
148
+ lines.push(` ${pc.dim(`# ${step++}. Verify the package (biome + tsc, scoped to this member)`)}`);
149
+ lines.push(` cd ${join(wsRoot, slug)}`);
150
+ lines.push(' npx tinycld-pkg check');
118
151
  lines.push('');
119
152
  lines.push(` ${pc.dim(`# ${step++}. Run the app (Expo + PocketBase, single-port dev proxy)`)}`);
120
- lines.push(' npm run start');
153
+ lines.push(` cd ${join(wsRoot, 'app')}`);
154
+ lines.push(' npm run dev');
121
155
  lines.push('');
122
156
  console.log(lines.join('\n'));
123
157
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACpD,OAAO,EAAgC,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACvF,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,MAAM,UAAU,cAAc,CAAC,IAA6B;IACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;IACvC,6EAA6E;IAC7E,6EAA6E;IAC7E,uEAAuE;IACvE,MAAM,OAAO,GAAG,gBAAgB,CAAC;QAC7B,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;QACvC,GAAG,IAAI;QACP,IAAI;KACP,CAAC,CAAA;IACF,8EAA8E;IAC9E,KAAK,MAAM,QAAQ,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,oCAAoC,QAAQ,kCAAkC,CAAC,CAAA;QACnG,CAAC;IACL,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,+BAA+B,IAAI,cAAc,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACvF,CAAC;AAED,KAAK,UAAU,IAAI;IACf,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAA;IAE7C,IAAI,IAAkC,CAAA;IACtC,IAAI,CAAC;QACD,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;YAC/B,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAA;YACvC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAC7B,OAAO,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;YACvD,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACnB,CAAC;QACD,MAAM,GAAG,CAAA;IACb,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,cAAc,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;QAC3D,KAAK,CACD,EAAE,CAAC,KAAK,CACJ,+CACI,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EACtD,qCAAqC,CACxC,CACJ,CAAA;QACD,OAAM;IACV,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAA;IAEtC,YAAY,CAAC,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAA;IAE5D,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC;QAClC,WAAW,EAAE,OAAO,CAAC,IAAI;QACzB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC;KAC9B,CAAC,CAAA;IAEF,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,SAAS,CAAA;IACjF,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAA;IAE/E,yEAAyE;IACzE,0EAA0E;IAC1E,wEAAwE;IACxE,qCAAqC;IACrC,MAAM,MAAM,GAAG,sBAAsB,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACtE,cAAc,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AACrE,CAAC;AAED,SAAS,sBAAsB,CAAC,SAAiB,EAAE,IAAY;IAC3D,0EAA0E;IAC1E,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IACnC,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ,CAAC,SAAS;QAAE,OAAO,QAAQ,CAAA;IACnF,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW,IAAI,SAAS,KAAK,QAAQ,CAAC,SAAS;QAAE,OAAO,WAAW,CAAA;IACzF,yEAAyE;IACzE,8DAA8D;IAC9D,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACjC,MAAM,aAAa,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAA;IAClE,OAAO,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAA;AACjD,CAAC;AAED,SAAS,eAAe,CAAC,IAAkC;IACvD,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,MAAM,CAAA;IACtC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAA;IACvC,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAA;IACtC,OAAO,QAAQ,CAAA;AACnB,CAAC;AASD,SAAS,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAkB;IACvE,MAAM,KAAK,GAAa,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC,CAAA;IACxD,IAAI,IAAI,GAAG,CAAC,CAAA;IAEZ,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;QACzB,gEAAgE;QAChE,iEAAiE;QACjE,kCAAkC;QAClC,KAAK,CAAC,IAAI,CACN,EAAE,CAAC,GAAG,CACF,sGAAsG,OAAO,CAAC,SAAS,CAAC,cAAc,CACzI,CACJ,CAAA;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,qCAAqC,CAAC,EAAE,CAAC,CAAA;IAC3E,KAAK,CAAC,IAAI,CAAC,QAAQ,SAAS,EAAE,CAAC,CAAA;IAC/B,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACxB,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IACzB,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAA;IACvD,KAAK,CAAC,IAAI,CAAC,4BAA4B,IAAI,6BAA6B,CAAC,CAAA;IACzE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAEd,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,mCAAmC,CAAC,EAAE,CAAC,CAAA;QACzE,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;QAC7B,KAAK,CAAC,IAAI,CAAC,8BAA8B,IAAI,EAAE,CAAC,CAAA;QAChD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,4EAA4E,CAAC,EAAE,CAAC,CAAA;IAClH,IAAI,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IACzC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;IAC9B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAEd,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,0DAA0D,CAAC,EAAE,CAAC,CAAA;IAChG,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAEd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;AACjC,CAAC;AAED,SAAS,YAAY;IACjB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC7B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IACxB,IAAI,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;IACtE,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAA;IAChB,CAAC;AACL,CAAC;AAED,IAAI,YAAY,EAAE,EAAE,CAAC;IACjB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACjB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC,CAAA;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACnB,CAAC,CAAC,CAAA;AACN,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/layout.js CHANGED
@@ -1,37 +1,24 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
- const APP_DIR_NAME = 'tinycld';
4
3
  export function detectLayout(slug, cwd = process.cwd()) {
5
- const candidateApp = join(cwd, APP_DIR_NAME);
6
- if (looksLikeAppShell(candidateApp)) {
7
- return {
8
- mode: 'attach',
9
- targetDir: resolve(cwd, slug),
10
- appDir: candidateApp,
11
- };
4
+ if (looksLikeWorkspaceRoot(cwd)) {
5
+ return { mode: 'attach', targetDir: resolve(cwd, slug), workspaceDir: cwd };
12
6
  }
13
- const wrapperDir = resolve(cwd, `${APP_DIR_NAME}-${slug}`);
7
+ const wrapperDir = resolve(cwd, `tinycld-${slug}`);
14
8
  return {
15
9
  mode: 'bootstrap',
16
10
  targetDir: join(wrapperDir, slug),
17
- appDir: join(wrapperDir, APP_DIR_NAME),
11
+ workspaceDir: wrapperDir,
18
12
  wrapperDir,
19
13
  };
20
14
  }
21
- /**
22
- * A directory "looks like" the tinycld app shell when it has a package.json
23
- * declaring `name: "tinycld"`. The bare directory check would false-match any
24
- * unrelated `tinycld` directory; the package.json check is fast (one read of
25
- * a file we'd be reading anyway) and unambiguous now that the app shell's
26
- * package.json `name` field has been renamed from "@tinycld/app" to "tinycld".
27
- */
28
- export function looksLikeAppShell(dir) {
15
+ /** A directory is a workspace root when its package.json name is "@tinycld/workspace". */
16
+ export function looksLikeWorkspaceRoot(dir) {
29
17
  const pkgPath = join(dir, 'package.json');
30
18
  if (!existsSync(pkgPath))
31
19
  return false;
32
20
  try {
33
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
34
- return pkg?.name === 'tinycld';
21
+ return JSON.parse(readFileSync(pkgPath, 'utf-8'))?.name === '@tinycld/workspace';
35
22
  }
36
23
  catch {
37
24
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAuBzC,MAAM,YAAY,GAAG,SAAS,CAAA;AAE9B,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IAClE,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;IAC5C,IAAI,iBAAiB,CAAC,YAAY,CAAC,EAAE,CAAC;QAClC,OAAO;YACH,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC;YAC7B,MAAM,EAAE,YAAY;SACvB,CAAA;IACL,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,YAAY,IAAI,IAAI,EAAE,CAAC,CAAA;IAC1D,OAAO;QACH,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC;QACjC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC;QACtC,UAAU;KACb,CAAA;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;IACzC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAA;IACtC,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;QACtD,OAAO,GAAG,EAAE,IAAI,KAAK,SAAS,CAAA;IAClC,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAA;IAChB,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAgBzC,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IAClE,IAAI,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,CAAA;IAC/E,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,WAAW,IAAI,EAAE,CAAC,CAAA;IAClD,OAAO;QACH,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC;QACjC,YAAY,EAAE,UAAU;QACxB,UAAU;KACb,CAAA;AACL,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;IACzC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAA;IACtC,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,IAAI,KAAK,oBAAoB,CAAA;IACpF,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAA;IAChB,CAAC;AACL,CAAC"}
@@ -1,85 +1,101 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
3
- import { dirname, join, relative } from 'node:path';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
4
  import { confirm, isCancel, log, spinner } from '@clack/prompts';
5
5
  import pc from 'picocolors';
6
- const APP_REPO_URL = 'git@github.com:tinycld/tinycld.git';
7
- const APP_DIR_NAME = 'tinycld';
6
+ import { bootstrapTooling } from "./bootstrap-tooling.js";
7
+ import { looksLikeWorkspaceRoot } from "./layout.js";
8
8
  /**
9
- * Returns true if the user chose to link (even if a subprocess failed —
10
- * the error is already logged, and we don't want to follow up with
11
- * "here are the manual steps" when the user already expressed intent).
12
- * Returns false only if linking was declined or skipped.
9
+ * Add `slug` to `<workspaceDir>/package.json` `workspaces[]` if it isn't already
10
+ * there. Idempotent, and a no-op when there's no package.json yet.
11
+ */
12
+ export function ensureMember(workspaceDir, slug) {
13
+ const pkgPath = join(workspaceDir, 'package.json');
14
+ if (!existsSync(pkgPath))
15
+ return;
16
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
17
+ pkg.workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : [];
18
+ if (!pkg.workspaces.includes(slug)) {
19
+ pkg.workspaces.push(slug);
20
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 4)}\n`);
21
+ }
22
+ }
23
+ /**
24
+ * Offer to link the freshly-scaffolded package into the TinyCld workspace.
25
+ *
26
+ * "Linking" in the workspace layout means: make the package a workspace member
27
+ * (its dir name appears in the root `package.json` `workspaces[]`) and run
28
+ * `npm install` at the workspace root — npm creates the `node_modules/@tinycld/*`
29
+ * symlink and the root `postinstall` runs the generator.
30
+ *
31
+ * Two shapes, distinguished by whether `workspaceDir` is already a workspace root:
32
+ * - attach: `workspaceDir` is already an assembled workspace root (its
33
+ * package.json name is `@tinycld/workspace`). No assembly — just ensure the
34
+ * member and install. The existing root package.json is left untouched.
35
+ * - bootstrap: `workspaceDir` is a fresh wrapper that only contains the
36
+ * scaffolded `<slug>/` so far. Assemble the workspace *around* it —
37
+ * `bootstrapTooling` generates the root scaffolding and clones app + core
38
+ * (no workspace meta-repo) — then ensure the new member and install. The
39
+ * `postinstall` (`cd app && …`) only succeeds once `app/` exists, which is
40
+ * why we assemble before installing.
13
41
  *
14
- * Linking happens against the tinycld **app shell** (sibling repo
15
- * `tinycld/tinycld`), which owns `packages:link` and bundles `@tinycld/core`
16
- * at `packages/@tinycld/core/`. This package's tsconfig
17
- * (`"@tinycld/core/*": ["../tinycld/packages/@tinycld/core/*"]`) and its Go
18
- * server's `replace tinycld.org/core => ../../tinycld/packages/@tinycld/core/server`
19
- * directive both resolve into the bundled core inside the app shell.
42
+ * Returns true if the user chose to link (even if a subprocess failed — the
43
+ * error is already logged, and we don't want to follow up with manual steps
44
+ * when the user already expressed intent). Returns false only when linking was
45
+ * declined or skipped.
20
46
  */
21
- export async function offerLinkPackage({ packageName, targetDir, mode }) {
47
+ export async function offerLinkPackage({ slug, workspaceDir, mode, assemble = realAssemble, install = realInstall, }) {
22
48
  if (mode === 'skip')
23
49
  return false;
24
- const parentDir = dirname(targetDir);
25
- const appDir = join(parentDir, APP_DIR_NAME);
26
- const appExists = existsSync(appDir);
50
+ // A wrapper that isn't yet a workspace root needs app + core assembled in.
51
+ const needsClone = !looksLikeWorkspaceRoot(workspaceDir);
27
52
  if (mode === 'prompt') {
28
- const prompt = appExists
29
- ? `Link ${pc.bold(packageName)} into ${pc.bold(APP_DIR_NAME)} now?`
30
- : `Clone ${pc.bold(APP_DIR_NAME)} (shallow) and link ${pc.bold(packageName)} into it now?`;
31
- const answer = await confirm({ message: prompt, initialValue: true });
53
+ const message = needsClone
54
+ ? `Assemble the tinycld workspace (app + core) and link ${pc.bold(slug)} now?`
55
+ : `Link ${pc.bold(slug)} into the workspace now?`;
56
+ const answer = await confirm({ message, initialValue: true });
32
57
  if (isCancel(answer) || answer !== true)
33
58
  return false;
34
59
  }
35
- if (!appExists) {
36
- if (!cloneRepo(APP_REPO_URL, appDir))
60
+ if (needsClone) {
61
+ const s = spinner();
62
+ s.start('Assembling the tinycld workspace (app + core)');
63
+ try {
64
+ assemble(workspaceDir);
65
+ }
66
+ catch (err) {
67
+ s.stop(pc.red('Workspace assembly failed'), 1);
68
+ log.error(err instanceof Error ? err.message : String(err));
37
69
  return true;
70
+ }
71
+ s.stop('Assembled the tinycld workspace');
38
72
  }
39
- else {
40
- log.info(`Using existing ${APP_DIR_NAME} at ${appDir}`);
41
- }
42
- const install = spinner();
43
- install.start(`Installing ${APP_DIR_NAME} dependencies (npm install)`);
44
- const inst = spawnSync('npm', ['install'], { cwd: appDir, stdio: 'pipe', encoding: 'utf8' });
45
- if (inst.status !== 0) {
46
- install.stop(pc.red('npm install failed'), 1);
47
- log.error(inst.stderr.trim() || 'npm install exited non-zero');
48
- return true;
49
- }
50
- install.stop(`Installed ${APP_DIR_NAME} dependencies`);
51
- // packages:link in tinycld takes a sibling-directory locator and reads
52
- // the package name from the sibling's package.json. Pass a path relative
53
- // to the app shell so the symlink target stays portable.
54
- const link = spinner();
55
- link.start(`Linking ${packageName}`);
56
- const locator = relative(appDir, targetDir) || targetDir;
57
- const linkResult = spawnSync('npm', ['run', 'packages:link', locator], {
58
- cwd: appDir,
59
- stdio: 'pipe',
60
- encoding: 'utf8',
61
- });
62
- if (linkResult.status !== 0) {
63
- link.stop(pc.red('Link failed'), 1);
64
- log.error(linkResult.stderr.trim() || 'packages:link exited non-zero');
73
+ ensureMember(workspaceDir, slug);
74
+ const i = spinner();
75
+ i.start('Installing workspace (npm install)');
76
+ if (!install(workspaceDir)) {
77
+ i.stop(pc.red('npm install failed'), 1);
65
78
  return true;
66
79
  }
67
- link.stop(`Linked ${packageName} into ${APP_DIR_NAME}`);
80
+ i.stop('Installed workspace');
81
+ log.success(`Linked ${pc.bold(slug)} into the workspace`);
68
82
  return true;
69
83
  }
70
- function cloneRepo(url, dest) {
71
- const s = spinner();
72
- s.start(`Cloning ${url} into ${dest}`);
73
- const result = spawnSync('git', ['clone', '--depth', '1', url, dest], {
74
- stdio: 'pipe',
75
- encoding: 'utf8',
76
- });
77
- if (result.status !== 0) {
78
- s.stop(pc.red('Clone failed'), 1);
79
- log.error(result.stderr.trim() || 'git clone exited non-zero');
84
+ /**
85
+ * Assemble the workspace skeleton at `dir`: write the workspace manifest and
86
+ * clone app + core (honoring `TINYCLD_REPO_BASE` for keyless/HTTPS envs).
87
+ * `bootstrapTooling` is synchronous and throws on failure (unknown member, etc.);
88
+ * `offerLinkPackage` wraps the call so the error surfaces in the spinner.
89
+ */
90
+ function realAssemble(dir) {
91
+ bootstrapTooling({ root: dir, repoBase: process.env.TINYCLD_REPO_BASE });
92
+ }
93
+ function realInstall(cwd) {
94
+ const r = spawnSync('npm', ['install'], { cwd, stdio: 'pipe', encoding: 'utf8' });
95
+ if (r.status !== 0) {
96
+ log.error(r.stderr?.trim() || 'npm install exited non-zero');
80
97
  return false;
81
98
  }
82
- s.stop(`Cloned ${dest}`);
83
99
  return true;
84
100
  }
85
101
  //# sourceMappingURL=link-package.js.map